<?php

namespace App\Services\Care\Workflow;

use App\Models\Appointment;
use App\Models\Bill;
use App\Models\FinancialObligation;
use App\Models\InvestigationRequest;
use App\Models\Organization;
use App\Models\Prescription;
use App\Models\Visit;
use App\Models\WorkflowStage;
use App\Services\Care\BillService;
use App\Services\Care\CareQueueBridge;
use App\Services\Care\CareQueueContexts;
use Illuminate\Support\Facades\Log;

/**
 * Releases the entity represented by the visit's current workflow stage to
 * Ladill Queue after stage entry or financial clearance. Also owns the cashier
 * booth queue for pay-before-service financial gates.
 */
class WorkflowQueueReleaseService
{
    public function __construct(
        protected WorkflowEngine $engine,
        protected CareQueueBridge $queue,
    ) {}

    public function releaseCurrent(Organization $organization, Visit $visit): void
    {
        $advance = $this->engine->currentAdvance($visit);
        $stage = $advance?->stage;
        if ($stage === null) {
            return;
        }

        // Hold at the cashier booth while a before-service gate is unpaid.
        if ($advance->isBlocked() && $advance->blocked_reason === 'awaiting_payment') {
            $this->enqueueCashier($organization, $visit, $stage);

            return;
        }

        if ($stage->queue_context === null) {
            if ($stage->type === 'payment') {
                $this->enqueueCashier($organization, $visit, $stage);
            }

            return;
        }

        $context = $stage->queue_context;

        if ($context === CareQueueContexts::CONSULTATION || CareQueueContexts::isSpecialty($context)) {
            $appointment = Appointment::query()
                ->where('visit_id', $visit->id)
                ->with(['patient', 'organization', 'practitioner', 'visit'])
                ->latest('id')
                ->first();

            if ($appointment) {
                if ($appointment->status === Appointment::STATUS_COMPLETED) {
                    $nextPosition = ((int) Appointment::owned($appointment->owner_ref)
                        ->where('branch_id', $appointment->branch_id)
                        ->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
                        ->max('queue_position')) + 1;
                    $appointment->forceFill([
                        'status' => Appointment::STATUS_WAITING,
                        'waiting_at' => now(),
                        'started_at' => null,
                        'completed_at' => null,
                        'queue_position' => $nextPosition,
                    ])->save();
                }

                if (filled($appointment->queue_ticket_uuid)
                    && in_array($appointment->queue_ticket_status, ['completed', 'cancelled'], true)) {
                    $appointment->forceFill([
                        'queue_ticket_uuid' => null,
                        'queue_ticket_number' => null,
                        'queue_ticket_status' => null,
                        'queue_service_point_uuid' => null,
                        'queue_destination' => null,
                        'queue_staff_display_name' => null,
                        'queue_routing_status' => null,
                    ])->save();
                }

                $this->queue->issueForAppointment($organization, $appointment);
            }

            return;
        }

        if ($context === CareQueueContexts::LABORATORY || $context === CareQueueContexts::IMAGING) {
            InvestigationRequest::query()
                ->where('visit_id', $visit->id)
                ->with(['patient', 'visit'])
                ->get()
                ->each(fn (InvestigationRequest $request) => $this->queue->issueForInvestigation($organization, $request));

            return;
        }

        if ($context === CareQueueContexts::PHARMACY) {
            Prescription::query()
                ->where('visit_id', $visit->id)
                ->with(['patient', 'visit'])
                ->get()
                ->each(fn (Prescription $prescription) => $this->queue->issueForPrescription($organization, $prescription));

            return;
        }

        if ($context === CareQueueContexts::BILLING) {
            $this->enqueueCashier($organization, $visit, $stage);
        }
    }

    /**
     * Ensure a visit blocked on payment has an open bill and an active cashier
     * booth ticket. No-ops when Care Queue Engine is disabled.
     */
    public function enqueueCashier(
        Organization $organization,
        Visit $visit,
        ?WorkflowStage $stage = null,
    ): ?Bill {
        if (! $this->queue->isEnabled($organization)) {
            return null;
        }

        $stage ??= $this->engine->currentStage($visit);
        if ($stage === null) {
            return null;
        }

        $obligation = FinancialObligation::query()
            ->where('visit_id', $visit->id)
            ->where('workflow_stage_id', $stage->id)
            ->whereNull('deleted_at')
            ->first();

        if ($obligation === null
            || $obligation->isCleared()
            || (int) $obligation->amount_minor <= 0) {
            return null;
        }

        try {
            $bill = app(BillService::class)->billForObligation(
                $obligation,
                (string) $visit->owner_ref,
                (string) $visit->owner_ref,
            );

            return $this->queue->issueForBill($organization, $bill->fresh(['patient', 'visit']));
        } catch (\Throwable $e) {
            Log::warning('care.cashier_queue_enqueue_failed', [
                'visit_id' => $visit->id,
                'stage_code' => $stage->code,
                'message' => $e->getMessage(),
            ]);

            return null;
        }
    }

    /**
     * Complete (or retire) the cashier booth ticket after a financial gate clears.
     * Non-payment clearances void unpaid obligation-only placeholder bills.
     */
    public function completeCashier(
        Organization $organization,
        FinancialObligation $obligation,
        string $clearanceMethod,
    ): void {
        if (! $this->queue->isEnabled($organization)) {
            return;
        }

        $obligation->loadMissing('bill');
        $bill = $obligation->bill;
        if ($bill === null) {
            return;
        }

        if (filled($bill->queue_ticket_uuid)
            && ! in_array($bill->queue_ticket_status, ['completed', 'cancelled'], true)) {
            $this->queue->complete($organization, $bill);
            $bill = $bill->fresh();
        }

        if (in_array($clearanceMethod, ['payment', 'bank_receipt'], true)) {
            return;
        }

        $bill = $bill->fresh(['lineItems']) ?? $bill;
        if ((int) $bill->amount_paid_minor > 0
            || in_array($bill->status, [Bill::STATUS_PAID, Bill::STATUS_VOID], true)) {
            return;
        }

        $hasForeignLines = $bill->lineItems
            ->contains(function ($item) use ($obligation) {
                return $item->source_type !== FinancialObligation::class
                    || (int) $item->source_id !== (int) $obligation->id;
            });

        if ($hasForeignLines) {
            return;
        }

        try {
            app(BillService::class)->void($bill, (string) $obligation->owner_ref, $obligation->cleared_by);
        } catch (\Throwable $e) {
            Log::warning('care.cashier_placeholder_bill_void_failed', [
                'bill_id' => $bill->id,
                'obligation_id' => $obligation->id,
                'message' => $e->getMessage(),
            ]);
        }
    }
}
