<?php

namespace App\Services\Care;

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

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

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

    /**
     * @param  array<string, mixed>  $filters
     */
    public function listDrugs(string $ownerRef, int $organizationId, array $filters = [], ?int $branchId = null): LengthAwarePaginator
    {
        $query = $this->queryDrugs($ownerRef, $organizationId)
            ->with(['batches'])
            ->orderBy('name');

        if ($branchId !== null) {
            $query->where(function (Builder $q) use ($branchId) {
                $q->where('branch_id', $branchId)->orWhereNull('branch_id');
            });
        }

        if ($search = trim((string) ($filters['q'] ?? ''))) {
            $query->where(function (Builder $inner) use ($search) {
                $inner->where('name', 'like', "%{$search}%")
                    ->orWhere('generic_name', 'like', "%{$search}%")
                    ->orWhere('sku', 'like', "%{$search}%");
            });
        }

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

    /**
     * @param  array<string, mixed>  $data
     */
    public function createDrug(Organization $organization, string $ownerRef, array $data): Drug
    {
        $drug = Drug::create([
            'owner_ref' => $ownerRef,
            'organization_id' => $organization->id,
            'branch_id' => $data['branch_id'] ?? null,
            'name' => $data['name'],
            'generic_name' => $data['generic_name'] ?? null,
            'sku' => $data['sku'] ?? null,
            'unit' => $data['unit'] ?? 'unit',
            'unit_price_minor' => (int) ($data['unit_price_minor'] ?? 0),
            'reorder_level' => (int) ($data['reorder_level'] ?? 10),
            'is_active' => (bool) ($data['is_active'] ?? true),
        ]);

        AuditLogger::record($ownerRef, 'drug.created', $organization->id, null, Drug::class, $drug->id);

        return $drug;
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function updateDrug(Drug $drug, string $ownerRef, array $data): Drug
    {
        $drug->update([
            'branch_id' => $data['branch_id'] ?? $drug->branch_id,
            'name' => $data['name'],
            'generic_name' => $data['generic_name'] ?? null,
            'sku' => $data['sku'] ?? null,
            'unit' => $data['unit'] ?? $drug->unit,
            'unit_price_minor' => (int) ($data['unit_price_minor'] ?? 0),
            'reorder_level' => (int) ($data['reorder_level'] ?? 10),
            'is_active' => (bool) ($data['is_active'] ?? true),
        ]);

        AuditLogger::record($ownerRef, 'drug.updated', $drug->organization_id, null, Drug::class, $drug->id);

        return $drug->fresh(['batches']);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function receiveBatch(Drug $drug, string $ownerRef, array $data, ?string $actorRef = null): DrugBatch
    {
        $batch = DrugBatch::create([
            'owner_ref' => $ownerRef,
            'drug_id' => $drug->id,
            'batch_number' => $data['batch_number'],
            'expiry_date' => $data['expiry_date'] ?? null,
            'quantity_on_hand' => (int) ($data['quantity_on_hand'] ?? 0),
            'cost_minor' => (int) ($data['cost_minor'] ?? 0),
            'received_at' => now(),
        ]);

        AuditLogger::record($ownerRef, 'drug.batch_received', $drug->organization_id, $actorRef, DrugBatch::class, $batch->id);

        return $batch->load('drug');
    }

    /**
     * @return Collection<int, Drug>
     */
    public function lowStock(string $ownerRef, int $organizationId): Collection
    {
        return $this->queryDrugs($ownerRef, $organizationId)
            ->where('is_active', true)
            ->with('batches')
            ->get()
            ->filter(fn (Drug $drug) => $drug->stockOnHand() <= $drug->reorder_level);
    }

    /**
     * @return Collection<int, DrugBatch>
     */
    public function expiredBatches(string $ownerRef, int $organizationId): Collection
    {
        return DrugBatch::owned($ownerRef)
            ->whereHas('drug', fn (Builder $q) => $q->where('organization_id', $organizationId))
            ->whereNotNull('expiry_date')
            ->where('expiry_date', '<', now()->toDateString())
            ->where('quantity_on_hand', '>', 0)
            ->with('drug')
            ->get();
    }

    /**
     * @param  array<int, array<string, mixed>>  $allocations
     */
    public function dispensePrescription(
        Prescription $prescription,
        string $ownerRef,
        array $allocations,
        ?string $actorRef = null,
    ): Prescription {
        if ($prescription->status !== Prescription::STATUS_ACTIVE) {
            throw new InvalidArgumentException('Only active prescriptions can be dispensed.');
        }

        $prescription->load('items');

        foreach ($allocations as $row) {
            if (empty($row['prescription_item_id']) || empty($row['drug_batch_id'])) {
                continue;
            }

            $item = $prescription->items->firstWhere('id', (int) $row['prescription_item_id']);
            if (! $item || $item->is_procedure) {
                continue;
            }

            $qty = max(1, (int) ($row['quantity'] ?? 1));
            $batch = DrugBatch::owned($ownerRef)->findOrFail((int) $row['drug_batch_id']);

            if ($batch->isExpired()) {
                throw new InvalidArgumentException("Batch {$batch->batch_number} is expired.");
            }

            if ($batch->quantity_on_hand < $qty) {
                throw new InvalidArgumentException("Insufficient stock for {$batch->drug->name}.");
            }

            $batch->decrement('quantity_on_hand', $qty);

            DispensingRecord::create([
                'owner_ref' => $ownerRef,
                'prescription_id' => $prescription->id,
                'prescription_item_id' => $item->id,
                'drug_batch_id' => $batch->id,
                'quantity' => $qty,
                'dispensed_by' => $actorRef,
                'dispensed_at' => now(),
            ]);

            if (! $item->drug_id) {
                $item->update(['drug_id' => $batch->drug_id]);
            }
        }

        $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;
    }
}
