<?php

namespace App\Services\Care;

use App\Models\Consultation;
use App\Models\Organization;
use App\Models\Prescription;
use App\Models\PrescriptionItem;
use App\Services\Care\Workflow\WorkflowStageCompletionService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use InvalidArgumentException;

class PrescriptionService
{
    public function __construct(
        protected CareQueueBridge $queueBridge,
        protected WorkflowStageCompletionService $workflowCompletion,
    ) {}

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

    /**
     * @param  array<string, mixed>  $filters
     */
    public function list(string $ownerRef, int $organizationId, array $filters = [], ?int $branchId = null): LengthAwarePaginator
    {
        $query = $this->queryForOrganization($ownerRef, $organizationId)
            ->with(['patient', 'practitioner', 'items'])
            ->orderByDesc('created_at');

        if ($status = $filters['status'] ?? null) {
            $query->where('status', $status);
        }

        if ($patientId = $filters['patient_id'] ?? null) {
            $query->where('patient_id', $patientId);
        }

        if ($branchId !== null) {
            $query->whereHas('visit', fn (Builder $q) => $q->where('branch_id', $branchId));
        }

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

    /**
     * @return Collection<int, Prescription>
     */
    public function pharmacyQueue(string $ownerRef, int $organizationId): Collection
    {
        return $this->queryForOrganization($ownerRef, $organizationId)
            ->where('status', Prescription::STATUS_ACTIVE)
            ->with(['patient', 'practitioner', 'items', 'visit.branch'])
            ->orderBy('created_at')
            ->get();
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function createFromConsultation(
        Consultation $consultation,
        string $ownerRef,
        array $data,
        ?string $actorRef = null,
    ): Prescription {
        $consultation->loadMissing('visit');

        $status = ($data['activate'] ?? true) ? Prescription::STATUS_ACTIVE : Prescription::STATUS_DRAFT;

        $prescription = Prescription::create([
            'owner_ref' => $ownerRef,
            'organization_id' => $consultation->visit->organization_id,
            'visit_id' => $consultation->visit_id,
            'consultation_id' => $consultation->id,
            'patient_id' => $consultation->patient_id,
            'practitioner_id' => $data['practitioner_id'] ?? $consultation->practitioner_id,
            'status' => $status,
            'notes' => $data['notes'] ?? null,
            'prescribed_by' => $actorRef,
        ]);

        $this->syncItems($prescription, $ownerRef, $data['items'] ?? []);

        AuditLogger::record(
            $ownerRef,
            'prescription.created',
            $consultation->visit->organization_id,
            $actorRef,
            Prescription::class,
            $prescription->id,
        );

        $prescription = $prescription->fresh(['items', 'patient', 'practitioner', 'visit']);
        if ($status === Prescription::STATUS_ACTIVE) {
            $organization = Organization::query()->find($prescription->organization_id);
            if ($organization) {
                $this->queueBridge->issueForPrescription($organization, $prescription);
            }
        }

        return $prescription->fresh(['items', 'patient', 'practitioner']);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function update(Prescription $prescription, string $ownerRef, array $data, ?string $actorRef = null): Prescription
    {
        if ($prescription->status !== Prescription::STATUS_DRAFT) {
            throw new InvalidArgumentException('Only draft prescriptions can be edited.');
        }

        $prescription->update([
            'practitioner_id' => $data['practitioner_id'] ?? $prescription->practitioner_id,
            'notes' => $data['notes'] ?? $prescription->notes,
        ]);

        if (array_key_exists('items', $data)) {
            $this->syncItems($prescription, $ownerRef, $data['items']);
        }

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

        return $prescription->fresh(['items', 'patient', 'practitioner']);
    }

    public function activate(Prescription $prescription, string $ownerRef, ?string $actorRef = null): Prescription
    {
        $this->assertStatus($prescription, Prescription::STATUS_DRAFT);

        $prescription->update(['status' => Prescription::STATUS_ACTIVE]);
        AuditLogger::record($ownerRef, 'prescription.activated', $prescription->organization_id, $actorRef, Prescription::class, $prescription->id);

        $prescription = $prescription->fresh(['items', 'patient', 'visit']);
        $organization = Organization::query()->find($prescription->organization_id);
        if ($organization) {
            $this->queueBridge->issueForPrescription($organization, $prescription);
        }

        return $prescription->fresh(['items', 'patient']);
    }

    public function dispense(Prescription $prescription, string $ownerRef, ?string $actorRef = null): Prescription
    {
        $this->assertStatus($prescription, Prescription::STATUS_ACTIVE);

        $prescription->update([
            'status' => Prescription::STATUS_DISPENSED,
            'dispensed_at' => now(),
            'dispensed_by' => $actorRef,
        ]);

        AuditLogger::record($ownerRef, 'prescription.dispensed', $prescription->organization_id, $actorRef, Prescription::class, $prescription->id);

        $prescription = $prescription->fresh(['items', 'patient', 'visit']);
        $organization = Organization::query()->find($prescription->organization_id);
        if ($organization) {
            $this->queueBridge->complete($organization, $prescription);
            if ($prescription->visit) {
                $this->workflowCompletion->complete(
                    $organization,
                    $prescription->visit,
                    CareQueueContexts::PHARMACY,
                    $ownerRef,
                    $actorRef,
                );
            }
        }

        return $prescription->fresh(['items', 'patient']);
    }

    public function cancel(Prescription $prescription, string $ownerRef, ?string $actorRef = null): Prescription
    {
        if (in_array($prescription->status, [Prescription::STATUS_DISPENSED, Prescription::STATUS_CANCELLED], true)) {
            throw new InvalidArgumentException('Prescription cannot be cancelled.');
        }

        $prescription->update(['status' => Prescription::STATUS_CANCELLED]);
        AuditLogger::record($ownerRef, 'prescription.cancelled', $prescription->organization_id, $actorRef, Prescription::class, $prescription->id);

        return $prescription->fresh(['items', 'patient']);
    }

    /**
     * @param  array<int, array<string, mixed>>  $items
     */
    protected function syncItems(Prescription $prescription, string $ownerRef, array $items): void
    {
        $prescription->items()->delete();

        foreach ($items as $index => $row) {
            if (empty($row['name'])) {
                continue;
            }

            PrescriptionItem::create([
                'owner_ref' => $ownerRef,
                'prescription_id' => $prescription->id,
                'is_procedure' => (bool) ($row['is_procedure'] ?? false),
                'name' => $row['name'],
                'dosage' => $row['dosage'] ?? null,
                'frequency' => $row['frequency'] ?? null,
                'duration' => $row['duration'] ?? null,
                'route' => $row['route'] ?? null,
                'quantity' => $row['quantity'] ?? null,
                'instructions' => $row['instructions'] ?? null,
                'sort_order' => $index,
            ]);
        }
    }

    protected function assertStatus(Prescription $prescription, string ...$allowed): void
    {
        if (! in_array($prescription->status, $allowed, true)) {
            throw new InvalidArgumentException("Cannot transition from {$prescription->status}.");
        }
    }
}
