<?php

namespace App\Services\Care;

use App\Models\EmergencyContact;
use App\Models\InsurancePolicy;
use App\Models\Consultation;
use App\Models\Organization;
use App\Models\Patient;
use App\Models\PatientAllergy;
use App\Models\PatientAttachment;
use App\Models\PatientCondition;
use App\Models\PatientFamilyHistory;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage;

class PatientService
{
    public function __construct(
        protected PatientNumberGenerator $numbers,
    ) {}

    public function queryForOrganization(string $ownerRef, int $organizationId): Builder
    {
        return Patient::owned($ownerRef)->where('organization_id', $organizationId);
    }

    /**
     * @param  array<string, mixed>  $filters
     * @param  list<int>|null  $practitionerIds  When set, only patients with appointments on these desks.
     */
    public function search(
        string $ownerRef,
        int $organizationId,
        array $filters = [],
        ?int $branchId = null,
        ?array $practitionerIds = null,
    ): LengthAwarePaginator {
        $query = $this->queryForOrganization($ownerRef, $organizationId)
            ->with(['branch'])
            ->orderByDesc('created_at');

        if ($branchId !== null) {
            $query->where('branch_id', $branchId);
        }

        if ($practitionerIds !== null) {
            if ($practitionerIds === []) {
                $query->whereRaw('1 = 0');
            } else {
                $query->whereHas(
                    'appointments',
                    fn (Builder $inner) => $inner->whereIn('practitioner_id', $practitionerIds),
                );
            }
        }

        if ($q = trim((string) ($filters['q'] ?? ''))) {
            $query->where(function (Builder $inner) use ($q) {
                $inner->where('patient_number', 'like', "%{$q}%")
                    ->orWhere('phone', 'like', "%{$q}%")
                    ->orWhere('national_id', 'like', "%{$q}%")
                    ->orWhere('first_name', 'like', "%{$q}%")
                    ->orWhere('last_name', 'like', "%{$q}%")
                    ->orWhere('other_names', 'like', "%{$q}%")
                    ->orWhereRaw("concat(first_name, ' ', coalesce(other_names, ''), ' ', last_name) like ?", ["%{$q}%"]);
            });
        }

        if ($patientNumber = trim((string) ($filters['patient_number'] ?? ''))) {
            $query->where('patient_number', $patientNumber);
        }

        if ($phone = trim((string) ($filters['phone'] ?? ''))) {
            $query->where('phone', 'like', "%{$phone}%");
        }

        if ($nationalId = trim((string) ($filters['national_id'] ?? ''))) {
            $query->where('national_id', $nationalId);
        }

        if ($dob = $filters['date_of_birth'] ?? null) {
            $query->whereDate('date_of_birth', Carbon::parse($dob)->toDateString());
        }

        return $query->paginate((int) ($filters['per_page'] ?? 20))->withQueryString();
    }

    /**
     * Exact patient_number match first, then fall back to broad search (first hit).
     */
    public function findByScanCode(string $ownerRef, int $organizationId, string $code, ?int $branchId = null): ?Patient
    {
        $code = trim($code);
        if ($code === '') {
            return null;
        }

        $base = $this->queryForOrganization($ownerRef, $organizationId)
            ->when($branchId !== null, fn ($q) => $q->where('branch_id', $branchId));

        $exact = (clone $base)->where('patient_number', $code)->first();
        if ($exact) {
            return $exact;
        }

        // Case-insensitive exact patient number.
        $ci = (clone $base)->whereRaw('lower(patient_number) = ?', [strtolower($code)])->first();
        if ($ci) {
            return $ci;
        }

        return (clone $base)
            ->where(function (Builder $inner) use ($code) {
                $inner->where('patient_number', 'like', "%{$code}%")
                    ->orWhere('national_id', $code)
                    ->orWhere('phone', 'like', "%{$code}%");
            })
            ->orderByDesc('created_at')
            ->first();
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function create(Organization $organization, string $ownerRef, array $data, ?string $actorRef = null): Patient
    {
        $patient = Patient::create([
            'owner_ref' => $ownerRef,
            'organization_id' => $organization->id,
            'branch_id' => $data['branch_id'] ?? null,
            'patient_number' => $this->numbers->generate($organization),
            'first_name' => $data['first_name'],
            'last_name' => $data['last_name'],
            'other_names' => $data['other_names'] ?? null,
            'gender' => $data['gender'] ?? null,
            'date_of_birth' => $data['date_of_birth'] ?? null,
            'phone' => $data['phone'] ?? null,
            'email' => $data['email'] ?? null,
            'national_id' => $data['national_id'] ?? null,
            'address' => $data['address'] ?? null,
            'city' => $data['city'] ?? null,
            'region' => $data['region'] ?? null,
            'notes' => $data['notes'] ?? null,
        ]);

        $this->syncRelated($patient, $ownerRef, $data);
        $this->storeAttachments($patient, $ownerRef, $data['attachments'] ?? [], $actorRef);

        AuditLogger::record($ownerRef, 'patient.created', $organization->id, $actorRef, Patient::class, $patient->id);

        return $patient->fresh([
            'allergies', 'conditions', 'familyHistory', 'emergencyContacts',
            'insurancePolicies', 'attachments', 'branch',
        ]);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function update(Patient $patient, string $ownerRef, array $data, ?string $actorRef = null): Patient
    {
        $patient->update([
            'branch_id' => $data['branch_id'] ?? $patient->branch_id,
            'first_name' => $data['first_name'],
            'last_name' => $data['last_name'],
            'other_names' => $data['other_names'] ?? null,
            'gender' => $data['gender'] ?? null,
            'date_of_birth' => $data['date_of_birth'] ?? null,
            'phone' => $data['phone'] ?? null,
            'email' => $data['email'] ?? null,
            'national_id' => $data['national_id'] ?? null,
            'address' => $data['address'] ?? null,
            'city' => $data['city'] ?? null,
            'region' => $data['region'] ?? null,
            'notes' => $data['notes'] ?? null,
        ]);

        $this->syncRelated($patient, $ownerRef, $data);
        $this->storeAttachments($patient, $ownerRef, $data['attachments'] ?? [], $actorRef);

        AuditLogger::record($ownerRef, 'patient.updated', $patient->organization_id, $actorRef, Patient::class, $patient->id);

        return $patient->fresh([
            'allergies', 'conditions', 'familyHistory', 'emergencyContacts',
            'insurancePolicies', 'attachments', 'branch',
        ]);
    }

    public function delete(Patient $patient, string $ownerRef, ?string $actorRef = null): void
    {
        $organizationId = $patient->organization_id;
        $patientId = $patient->id;

        foreach ($patient->attachments as $attachment) {
            Storage::disk('public')->delete($attachment->file_path);
        }

        $patient->delete();

        AuditLogger::record($ownerRef, 'patient.deleted', $organizationId, $actorRef, Patient::class, $patientId);
    }

    /**
     * @return array<string, mixed>
     */
    public function dashboard(Patient $patient): array
    {
        $patient->load([
            'branch', 'allergies', 'conditions', 'familyHistory',
            'emergencyContacts', 'insurancePolicies', 'attachments',
        ]);

        $visits = $patient->visits()
            ->with(['branch', 'careUnit', 'bed'])
            ->orderByDesc('checked_in_at')
            ->limit(10)
            ->get();

        $consultations = Consultation::owned($patient->owner_ref)
            ->where('patient_id', $patient->id)
            ->with(['practitioner', 'diagnoses'])
            ->orderByDesc('started_at')
            ->limit(10)
            ->get();

        $laboratory = $patient->investigationRequests()
            ->with(['investigationType', 'result'])
            ->whereIn('status', [
                \App\Models\InvestigationRequest::STATUS_COMPLETED,
                \App\Models\InvestigationRequest::STATUS_DELIVERED,
            ])
            ->orderByDesc('completed_at')
            ->limit(10)
            ->get();

        $prescriptions = $patient->prescriptions()
            ->with(['practitioner', 'items'])
            ->orderByDesc('created_at')
            ->limit(10)
            ->get();

        $invoices = $patient->bills()
            ->with(['branch', 'payments'])
            ->orderByDesc('created_at')
            ->limit(10)
            ->get();

        return [
            'patient' => $patient,
            'visits' => $visits,
            'consultations' => $consultations,
            'laboratory' => $laboratory,
            'prescriptions' => $prescriptions,
            'invoices' => $invoices,
        ];
    }

    /**
     * @param  array<string, mixed>  $data
     */
    protected function syncRelated(Patient $patient, string $ownerRef, array $data): void
    {
        if (array_key_exists('allergies', $data)) {
            $patient->allergies()->delete();
            foreach ($data['allergies'] as $row) {
                if (empty($row['allergen'])) {
                    continue;
                }
                PatientAllergy::create([
                    'owner_ref' => $ownerRef,
                    'patient_id' => $patient->id,
                    'allergen' => $row['allergen'],
                    'severity' => $row['severity'] ?? 'unknown',
                    'notes' => $row['notes'] ?? null,
                ]);
            }
        }

        if (array_key_exists('conditions', $data)) {
            $patient->conditions()->delete();
            foreach ($data['conditions'] as $row) {
                if (empty($row['condition'])) {
                    continue;
                }
                PatientCondition::create([
                    'owner_ref' => $ownerRef,
                    'patient_id' => $patient->id,
                    'condition' => $row['condition'],
                    'onset_date' => $row['onset_date'] ?? null,
                    'is_chronic' => (bool) ($row['is_chronic'] ?? false),
                    'notes' => $row['notes'] ?? null,
                ]);
            }
        }

        if (array_key_exists('family_history', $data)) {
            $patient->familyHistory()->delete();
            foreach ($data['family_history'] as $row) {
                if (empty($row['condition'])) {
                    continue;
                }
                PatientFamilyHistory::create([
                    'owner_ref' => $ownerRef,
                    'patient_id' => $patient->id,
                    'relation' => $row['relation'] ?? 'other',
                    'condition' => $row['condition'],
                    'notes' => $row['notes'] ?? null,
                ]);
            }
        }

        if (array_key_exists('emergency_contacts', $data)) {
            $patient->emergencyContacts()->delete();
            foreach ($data['emergency_contacts'] as $row) {
                if (empty($row['name']) || empty($row['phone'])) {
                    continue;
                }
                EmergencyContact::create([
                    'owner_ref' => $ownerRef,
                    'patient_id' => $patient->id,
                    'name' => $row['name'],
                    'phone' => $row['phone'],
                    'relationship' => $row['relationship'] ?? null,
                    'is_primary' => (bool) ($row['is_primary'] ?? false),
                ]);
            }
        }

        if (array_key_exists('insurance', $data)) {
            $patient->insurancePolicies()->delete();
            foreach ($data['insurance'] as $row) {
                if (empty($row['provider_name'])) {
                    continue;
                }
                InsurancePolicy::create([
                    'owner_ref' => $ownerRef,
                    'patient_id' => $patient->id,
                    'provider_name' => $row['provider_name'],
                    'policy_number' => $row['policy_number'] ?? null,
                    'coverage_type' => $row['coverage_type'] ?? null,
                    'expiry_date' => $row['expiry_date'] ?? null,
                    'notes' => $row['notes'] ?? null,
                ]);
            }
        }
    }

    /**
     * @param  array<int, UploadedFile>  $files
     */
    protected function storeAttachments(Patient $patient, string $ownerRef, array $files, ?string $actorRef): void
    {
        foreach ($files as $file) {
            if (! $file instanceof UploadedFile) {
                continue;
            }

            $path = $file->store("care/patients/{$patient->id}/attachments", 'public');

            PatientAttachment::create([
                'owner_ref' => $ownerRef,
                'patient_id' => $patient->id,
                'file_path' => $path,
                'original_name' => $file->getClientOriginalName(),
                'mime_type' => $file->getMimeType(),
                'document_type' => 'other',
                'uploaded_by' => $actorRef,
            ]);
        }
    }
}
