<?php

namespace Tests\Feature;

use App\Http\Middleware\EnsurePlatformSession;
use App\Models\Appointment;
use App\Models\Bill;
use App\Models\Branch;
use App\Models\FinancialObligation;
use App\Models\InvestigationRequest;
use App\Models\InvestigationType;
use App\Models\Member;
use App\Models\Organization;
use App\Models\Patient;
use App\Models\Payment;
use App\Models\User;
use App\Models\Visit;
use App\Models\VisitStageAdvance;
use App\Services\Care\AppointmentService;
use App\Services\Care\CareFeatures;
use App\Services\Care\CareQueueBridge;
use App\Services\Care\CareQueueContexts;
use App\Services\Care\VisitService;
use App\Services\Care\Workflow\FinancialGateService;
use App\Services\Care\Workflow\WorkflowEngine;
use App\Services\Care\Workflow\WorkflowQueueGate;
use App\Services\Care\Workflow\WorkflowStageCompletionService;
use App\Services\Care\Workflow\WorkflowTemplateInstaller;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;

class CareFinancialWorkflowRuntimeTest extends TestCase
{
    use RefreshDatabase;

    protected User $owner;

    protected Organization $organization;

    protected Branch $branch;

    protected Patient $patient;

    protected function setUp(): void
    {
        parent::setUp();
        $this->withoutMiddleware(EnsurePlatformSession::class);

        $this->owner = User::create([
            'public_id' => 'financial-workflow-owner',
            'name' => 'Workflow Admin',
            'email' => 'financial-workflow@example.com',
        ]);

        $this->organization = Organization::create([
            'owner_ref' => $this->owner->public_id,
            'name' => 'Financial Gate Hospital',
            'slug' => 'financial-gate-hospital',
            'settings' => [
                'onboarded' => true,
                'facility_category' => 'hospital',
                'plan' => 'pro',
                'plan_expires_at' => now()->addMonth()->toIso8601String(),
                'queue_integration_enabled' => true,
                'rollout' => [
                    CareFeatures::WORKFLOW_ENGINE => true,
                    CareFeatures::FINANCIAL_GATES => true,
                ],
            ],
        ]);

        Member::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'user_ref' => $this->owner->public_id,
            'role' => 'hospital_admin',
        ]);

        $this->branch = Branch::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'name' => 'Main',
            'is_active' => true,
        ]);

        $this->patient = Patient::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'WF-0001',
            'first_name' => 'Akosua',
            'last_name' => 'Boateng',
        ]);

        app(WorkflowTemplateInstaller::class)
            ->install($this->organization, $this->branch, 'public_hospital_gh');
    }

    protected function checkIn(): Visit
    {
        return app(VisitService::class)->checkIn(
            $this->organization,
            $this->owner->public_id,
            $this->patient,
            $this->branch->id,
            $this->owner->public_id,
        );
    }

    protected function reachConsultation(Visit $visit): FinancialObligation
    {
        $engine = app(WorkflowEngine::class);
        $gate = app(FinancialGateService::class);

        $registration = FinancialObligation::query()
            ->where('visit_id', $visit->id)
            ->where('stage_code', 'registration')
            ->firstOrFail();
        $gate->clear($registration, 'override', $this->owner->public_id);
        $engine->refreshGate($visit);
        $engine->advance($visit); // triage
        $engine->advance($visit); // consultation

        return FinancialObligation::query()
            ->where('visit_id', $visit->id)
            ->where('stage_code', 'consultation')
            ->firstOrFail();
    }

    public function test_check_in_starts_workflow_and_blocks_initial_queue_release(): void
    {
        $visit = $this->checkIn();

        $advance = app(WorkflowEngine::class)->currentAdvance($visit);
        $this->assertSame('registration', $advance->stage_code);
        $this->assertSame(VisitStageAdvance::STATUS_BLOCKED, $advance->status);
        $this->assertDatabaseHas('care_financial_obligations', [
            'visit_id' => $visit->id,
            'stage_code' => 'registration',
            'status' => FinancialObligation::STATUS_PENDING,
        ]);
        $this->assertFalse(app(WorkflowQueueGate::class)->canRelease(
            $this->organization,
            $visit,
            CareQueueContexts::CONSULTATION,
        ));
    }

    public function test_queue_bridge_cannot_backfill_a_gated_appointment(): void
    {
        Http::fake();
        $visit = $this->checkIn();
        $appointment = Appointment::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $this->patient->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_WAITING,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'waiting_at' => now(),
            'queue_position' => 1,
        ]);

        app(CareQueueBridge::class)->issueForAppointment($this->organization, $appointment);

        Http::assertNothingSent();
        $this->assertNull($appointment->fresh()->queue_ticket_uuid);
    }

    public function test_appointment_page_shows_current_workflow_stage(): void
    {
        $visit = $this->checkIn();
        $appointment = Appointment::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $this->patient->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_WAITING,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'waiting_at' => now(),
            'queue_position' => 1,
        ]);

        $this->actingAs($this->owner)
            ->get(route('care.appointments.show', $appointment))
            ->assertOk()
            ->assertSee('Patient journey')
            ->assertSee('Registration')
            ->assertSee('financial clearance required')
            ->assertDontSee('Start consultation');
    }

    public function test_blocked_visit_cannot_advance_until_current_gate_is_cleared(): void
    {
        $visit = $this->checkIn();

        $this->actingAs($this->owner)
            ->from(route('care.appointments.index'))
            ->post(route('care.visits.workflow.advance', $visit))
            ->assertRedirect(route('care.appointments.index'))
            ->assertSessionHas('error');

        $this->assertSame('registration', app(WorkflowEngine::class)->currentAdvance($visit)->stage_code);
    }

    public function test_cashier_clearance_creates_bill_payment_and_releases_stage(): void
    {
        $visit = $this->checkIn();
        $consultation = $this->reachConsultation($visit);

        $this->actingAs($this->owner)
            ->get(route('care.obligations.index'))
            ->assertOk()
            ->assertSee('Financial gates')
            ->assertSee('Akosua Boateng')
            ->assertSee('Consultation fee');

        $this->actingAs($this->owner)
            ->post(route('care.obligations.clear', $consultation), [
                'method' => 'payment',
                'payment_mode' => 'internal_cashier',
                'payment_method' => 'cash',
                'reference' => 'CASH-1001',
            ])
            ->assertRedirect()
            ->assertSessionHas('success');

        $consultation->refresh();
        $this->assertSame(FinancialObligation::STATUS_PAID, $consultation->status);
        $this->assertNotNull($consultation->bill_id);
        $this->assertDatabaseHas('care_bill_line_items', [
            'bill_id' => $consultation->bill_id,
            'source_type' => FinancialObligation::class,
            'source_id' => $consultation->id,
            'total_minor' => (int) config('care.billing.consultation_fee_minor'),
        ]);
        $this->assertDatabaseHas('care_payments', [
            'bill_id' => $consultation->bill_id,
            'method' => Payment::METHOD_CASH,
            'status' => Payment::STATUS_PAID,
            'reference' => 'CASH-1001',
        ]);
        $this->assertSame(Bill::STATUS_PAID, $consultation->bill->fresh()->status);
        $this->assertSame(VisitStageAdvance::STATUS_ACTIVE, app(WorkflowEngine::class)->currentAdvance($visit)->status);
        $this->assertTrue(app(WorkflowQueueGate::class)->canRelease(
            $this->organization,
            $visit,
            CareQueueContexts::CONSULTATION,
        ));

        // A repeated submission must not create a second financial payment.
        $this->actingAs($this->owner)
            ->post(route('care.obligations.clear', $consultation), [
                'method' => 'payment',
                'payment_mode' => 'internal_cashier',
                'payment_method' => 'cash',
            ])
            ->assertSessionHas('info');
        $this->assertSame(1, Payment::query()->where('bill_id', $consultation->bill_id)->count());
    }

    public function test_clinical_stage_cannot_be_manually_skipped(): void
    {
        $visit = $this->checkIn();
        $consultation = $this->reachConsultation($visit);
        app(FinancialGateService::class)->clear($consultation, 'insurance', $this->owner->public_id);
        app(WorkflowEngine::class)->refreshGate($visit);

        $this->actingAs($this->owner)
            ->post(route('care.visits.workflow.advance', $visit))
            ->assertSessionHas('error');

        $this->assertSame('consultation', app(WorkflowEngine::class)->currentAdvance($visit)->stage_code);
    }

    public function test_non_insurance_stage_rejects_insurance_clearance(): void
    {
        app(WorkflowTemplateInstaller::class)
            ->install($this->organization, $this->branch, 'herbal_hospital');
        $visit = $this->checkIn();
        $obligation = FinancialObligation::query()->where('visit_id', $visit->id)->firstOrFail();

        $this->actingAs($this->owner)
            ->post(route('care.obligations.clear', $obligation), [
                'method' => 'insurance',
            ])
            ->assertSessionHasErrors('method');

        $this->assertSame(FinancialObligation::STATUS_PENDING, $obligation->fresh()->status);
    }

    public function test_lab_stage_obligation_uses_requested_test_prices(): void
    {
        $visit = $this->checkIn();
        $consultation = $this->reachConsultation($visit);
        app(FinancialGateService::class)->clear($consultation, 'insurance', $this->owner->public_id);
        app(WorkflowEngine::class)->refreshGate($visit);

        $type = InvestigationType::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'name' => 'Full blood count',
            'code' => 'FBC',
            'category' => 'blood',
            'price_minor' => 3500,
            'is_active' => true,
        ]);
        InvestigationRequest::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'visit_id' => $visit->id,
            'patient_id' => $this->patient->id,
            'investigation_type_id' => $type->id,
            'status' => InvestigationRequest::STATUS_PENDING,
            'priority' => 'routine',
        ]);

        $advance = app(WorkflowEngine::class)->advance($visit, 'laboratory');
        $this->assertSame(VisitStageAdvance::STATUS_BLOCKED, $advance->status);
        $this->assertDatabaseHas('care_financial_obligations', [
            'visit_id' => $visit->id,
            'stage_code' => 'laboratory',
            'amount_minor' => 3500,
            'status' => FinancialObligation::STATUS_PENDING,
        ]);
    }

    public function test_imaging_requests_use_the_imaging_queue_context(): void
    {
        $visit = $this->checkIn();
        $type = InvestigationType::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'name' => 'Chest X-ray',
            'code' => 'CXR',
            'category' => 'xray',
            'price_minor' => 8000,
            'is_active' => true,
        ]);
        $request = InvestigationRequest::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'visit_id' => $visit->id,
            'patient_id' => $this->patient->id,
            'investigation_type_id' => $type->id,
            'status' => InvestigationRequest::STATUS_PENDING,
            'priority' => 'routine',
        ]);

        $this->assertSame(
            CareQueueContexts::IMAGING,
            app(CareQueueBridge::class)->contextForInvestigation($request),
        );
    }

    public function test_settings_exposes_workflow_controls_and_herbal_categories(): void
    {
        $this->actingAs($this->owner)
            ->get(route('care.settings'))
            ->assertOk()
            ->assertSee('Patient journey workflow')
            ->assertSee('Enforce payment and authorization gates')
            ->assertSee('Herbal hospital')
            ->assertSee('Herbal clinic');
    }

    public function test_completing_consultation_appointment_advances_instead_of_closing_visit(): void
    {
        Http::fake();
        $visit = $this->checkIn();
        $consultationObligation = $this->reachConsultation($visit);
        app(FinancialGateService::class)->clear($consultationObligation, 'payment', $this->owner->public_id);
        app(WorkflowEngine::class)->refreshGate($visit);

        $appointment = Appointment::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $this->patient->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_IN_CONSULTATION,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'waiting_at' => now(),
            'started_at' => now(),
            'queue_position' => 1,
        ]);

        app(AppointmentService::class)->complete(
            $appointment,
            $this->owner->public_id,
            $this->owner->public_id,
        );

        $this->assertSame(Appointment::STATUS_COMPLETED, $appointment->fresh()->status);
        $this->assertNotSame(Visit::STATUS_COMPLETED, $visit->fresh()->status);
        $this->assertSame('pharmacy', app(WorkflowEngine::class)->currentAdvance($visit)->stage_code);
    }

    public function test_completed_lab_stage_returns_patient_to_a_new_consultation_queue(): void
    {
        Http::fake();
        $visit = $this->checkIn();
        $consultation = $this->reachConsultation($visit);
        app(FinancialGateService::class)->clear($consultation, 'insurance', $this->owner->public_id);
        app(WorkflowEngine::class)->refreshGate($visit);

        $appointment = Appointment::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $this->patient->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_COMPLETED,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'completed_at' => now(),
            'queue_position' => 1,
        ]);
        $type = InvestigationType::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'name' => 'Malaria test',
            'code' => 'MAL',
            'category' => 'blood',
            'price_minor' => 3000,
            'is_active' => true,
        ]);
        InvestigationRequest::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'visit_id' => $visit->id,
            'patient_id' => $this->patient->id,
            'investigation_type_id' => $type->id,
            'status' => InvestigationRequest::STATUS_COMPLETED,
            'priority' => 'routine',
        ]);

        $labAdvance = app(WorkflowEngine::class)->advance($visit, 'laboratory');
        $labObligation = FinancialObligation::query()
            ->where('workflow_stage_id', $labAdvance->workflow_stage_id)
            ->where('visit_id', $visit->id)
            ->firstOrFail();
        app(FinancialGateService::class)->clear($labObligation, 'insurance', $this->owner->public_id);
        app(WorkflowEngine::class)->refreshGate($visit);

        app(WorkflowStageCompletionService::class)->complete(
            $this->organization,
            $visit,
            CareQueueContexts::LABORATORY,
            $this->owner->public_id,
            $this->owner->public_id,
        );

        $this->assertSame('consultation', app(WorkflowEngine::class)->currentAdvance($visit)->stage_code);
        $this->assertSame(Appointment::STATUS_WAITING, $appointment->fresh()->status);
    }

    public function test_disabled_workflow_keeps_legacy_check_in_behavior(): void
    {
        $settings = $this->organization->settings;
        data_set($settings, 'rollout.'.CareFeatures::WORKFLOW_ENGINE, false);
        data_set($settings, 'rollout.'.CareFeatures::FINANCIAL_GATES, false);
        $this->organization->update(['settings' => $settings]);

        $visit = $this->checkIn();

        $this->assertDatabaseMissing('care_visit_stage_advances', ['visit_id' => $visit->id]);
        $this->assertTrue(app(WorkflowQueueGate::class)->canRelease(
            $this->organization->fresh(),
            $visit,
            CareQueueContexts::CONSULTATION,
        ));
    }

    public function test_enabling_financial_gates_rechecks_an_active_visit_stage(): void
    {
        $settings = $this->organization->settings;
        data_set($settings, 'rollout.'.CareFeatures::FINANCIAL_GATES, false);
        $this->organization->update(['settings' => $settings]);

        $visit = $this->checkIn();
        $this->assertSame(VisitStageAdvance::STATUS_ACTIVE, app(WorkflowEngine::class)->currentAdvance($visit)->status);
        $this->assertDatabaseMissing('care_financial_obligations', ['visit_id' => $visit->id]);

        data_set($settings, 'rollout.'.CareFeatures::FINANCIAL_GATES, true);
        $this->organization->update(['settings' => $settings]);

        $advance = app(WorkflowEngine::class)->refreshGate($visit);
        $this->assertSame(VisitStageAdvance::STATUS_BLOCKED, $advance->status);
        $this->assertDatabaseHas('care_financial_obligations', [
            'visit_id' => $visit->id,
            'stage_code' => 'registration',
            'status' => FinancialObligation::STATUS_PENDING,
        ]);
    }

    public function test_awaiting_payment_issues_a_billing_cashier_ticket(): void
    {
        Http::fake();
        $visit = $this->checkIn();

        $obligation = FinancialObligation::query()
            ->where('visit_id', $visit->id)
            ->where('stage_code', 'registration')
            ->firstOrFail();

        $this->assertNotNull($obligation->bill_id);
        $bill = $obligation->bill()->first();
        $this->assertNotNull($bill);
        $this->assertNotNull($bill->queue_ticket_uuid);
        $this->assertSame('waiting', $bill->queue_ticket_status);
        $this->assertStringStartsWith('B', (string) $bill->queue_ticket_number);
        $this->assertDatabaseHas('care_queue_tickets', [
            'uuid' => $bill->queue_ticket_uuid,
            'ticket_number' => $bill->queue_ticket_number,
            'status' => 'waiting',
            'care_entity' => 'bill',
            'care_entity_id' => $bill->id,
        ]);
        $this->assertDatabaseHas('care_service_queues', [
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'context' => CareQueueContexts::BILLING,
        ]);
        $this->assertTrue(app(WorkflowQueueGate::class)->canRelease(
            $this->organization,
            $visit,
            CareQueueContexts::BILLING,
        ));
        $this->assertFalse(app(WorkflowQueueGate::class)->canRelease(
            $this->organization,
            $visit,
            CareQueueContexts::CONSULTATION,
        ));
        Http::assertNothingSent();
    }

    public function test_queue_off_keeps_financial_gate_without_cashier_ticket(): void
    {
        $settings = $this->organization->settings;
        $settings['queue_integration_enabled'] = false;
        $this->organization->update(['settings' => $settings]);

        $visit = $this->checkIn();
        $obligation = FinancialObligation::query()
            ->where('visit_id', $visit->id)
            ->where('stage_code', 'registration')
            ->firstOrFail();

        $this->assertSame(FinancialObligation::STATUS_PENDING, $obligation->status);
        $this->assertNull($obligation->bill_id);
        $this->assertSame(0, Bill::query()->where('visit_id', $visit->id)->count());
        $this->assertSame(VisitStageAdvance::STATUS_BLOCKED, app(WorkflowEngine::class)->currentAdvance($visit)->status);
    }

    public function test_clearing_gate_completes_cashier_ticket_and_releases_clinical_queue(): void
    {
        Http::fake();
        $visit = $this->checkIn();
        $consultation = $this->reachConsultation($visit);
        $consultation->refresh();
        $this->assertNotNull($consultation->bill_id);
        $bill = $consultation->bill()->firstOrFail();
        $this->assertNotNull($bill->queue_ticket_uuid);
        $ticketUuid = $bill->queue_ticket_uuid;

        $appointment = Appointment::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $this->patient->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_WAITING,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'waiting_at' => now(),
            'queue_position' => 1,
        ]);

        $this->actingAs($this->owner)
            ->post(route('care.obligations.clear', $consultation), [
                'method' => 'payment',
                'payment_mode' => 'internal_cashier',
                'payment_method' => 'cash',
                'reference' => 'CASH-QUEUE-1',
            ])
            ->assertRedirect()
            ->assertSessionHas('success');

        $bill->refresh();
        $this->assertSame('completed', $bill->queue_ticket_status);
        $this->assertDatabaseHas('care_queue_tickets', [
            'uuid' => $ticketUuid,
            'status' => 'completed',
        ]);
        $this->assertSame(VisitStageAdvance::STATUS_ACTIVE, app(WorkflowEngine::class)->currentAdvance($visit)->status);
        $this->assertTrue(app(WorkflowQueueGate::class)->canRelease(
            $this->organization,
            $visit,
            CareQueueContexts::CONSULTATION,
        ));

        $appointment->refresh();
        $this->assertNotNull($appointment->queue_ticket_uuid);
        $this->assertSame('waiting', $appointment->queue_ticket_status);
        $this->assertStringStartsWith('C', (string) $appointment->queue_ticket_number);
    }

    public function test_cashier_can_call_next_and_serve_billing_ticket(): void
    {
        Http::fake();

        Member::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'user_ref' => 'cashier-queue-user',
            'role' => 'cashier',
            'branch_id' => $this->branch->id,
        ]);
        $cashier = User::create([
            'public_id' => 'cashier-queue-user',
            'name' => 'Cashier',
            'email' => 'cashier-queue@example.com',
        ]);

        $visit = $this->checkIn();
        $obligation = FinancialObligation::query()
            ->where('visit_id', $visit->id)
            ->where('stage_code', 'registration')
            ->firstOrFail();
        $bill = $obligation->bill()->firstOrFail();
        $this->assertNotNull($bill->queue_ticket_uuid);

        $this->actingAs($cashier)
            ->post(route('care.bills.call-next'), ['branch_id' => $this->branch->id])
            ->assertRedirect()
            ->assertSessionHas('success');

        $bill->refresh();
        $this->assertSame('called', $bill->queue_ticket_status);

        $this->actingAs($cashier)
            ->post(route('care.bills.serve', $bill))
            ->assertRedirect()
            ->assertSessionHas('success');

        $this->assertSame('serving', $bill->fresh()->queue_ticket_status);
    }
}
