<?php

namespace Tests\Feature;

use App\Http\Middleware\EnsurePlatformSession;
use App\Models\Appointment;
use App\Models\Branch;
use App\Models\CareQueueTicket;
use App\Models\CareServicePoint;
use App\Models\CareServiceQueue;
use App\Models\Department;
use App\Models\Member;
use App\Models\Organization;
use App\Models\Patient;
use App\Models\Practitioner;
use App\Models\User;
use App\Services\Care\AppointmentService;
use App\Services\Care\CareQueueBridge;
use App\Services\Care\CareQueueContexts;
use App\Services\Care\CareQueueProvisioner;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Tests\TestCase;

class CareServicePointRoutingTest extends TestCase
{
    use RefreshDatabase;

    protected User $owner;

    protected Organization $organization;

    protected Branch $branch;

    protected Practitioner $doctorA;

    protected Practitioner $doctorB;

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

        $this->owner = User::create([
            'public_id' => 'sp-care-owner',
            'name' => 'Owner',
            'email' => 'sp-care@example.com',
        ]);

        $this->organization = Organization::create([
            'owner_ref' => $this->owner->public_id,
            'name' => 'Ridge Medical Centre',
            'slug' => 'ridge-medical',
            'settings' => [
                'onboarded' => true,
                'facility_type' => 'hospital',
                'plan' => 'pro',
                'plan_expires_at' => now()->addMonth()->toIso8601String(),
                'queue_integration_enabled' => 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->doctorA = Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'name' => 'Dr. Mensah',
            'specialty' => 'General',
            'room' => 'Consultation Room 4',
            'user_ref' => 'doc-mensah',
            'is_active' => true,
        ]);

        $this->doctorB = Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'name' => 'Dr. Okai',
            'specialty' => 'Pediatrics',
            'room' => 'Consultation Room 5',
            'user_ref' => 'doc-okai',
            'is_active' => true,
        ]);
    }

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

        $patient = Patient::create([
            'uuid' => (string) Str::uuid(),
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-1',
            'first_name' => 'Ada',
            'last_name' => 'Lovelace',
        ]);

        $appointment = Appointment::create([
            'uuid' => (string) Str::uuid(),
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'practitioner_id' => $this->doctorA->id,
            'type' => Appointment::TYPE_SCHEDULED,
            'status' => Appointment::STATUS_SCHEDULED,
            'scheduled_at' => now()->addHour(),
        ]);

        $result = app(AppointmentService::class)->checkIn($appointment, $this->owner->public_id);

        $point = CareServicePoint::query()
            ->where('organization_id', $this->organization->id)
            ->where('kind', 'practitioner')
            ->where('ref_id', $this->doctorA->id)
            ->first();

        $this->assertNotNull($point);
        $this->assertSame('C001', $result->queue_ticket_number);
        $this->assertSame($point->uuid, $result->queue_service_point_uuid);
        $this->assertSame('Consultation Room 4', $result->queue_destination);
        $this->assertSame('Dr. Mensah', $result->queue_staff_display_name);
        $this->assertSame(CareQueueContexts::ROUTING_ROUTED, $result->queue_routing_status);
        Http::assertNothingSent();
    }

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

        $patient = Patient::create([
            'uuid' => (string) Str::uuid(),
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-2',
            'first_name' => 'Bob',
            'last_name' => 'Builder',
        ]);

        $appointment = Appointment::create([
            'uuid' => (string) Str::uuid(),
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'practitioner_id' => null,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_WAITING,
            'waiting_at' => now(),
        ]);

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

        // Native engine prefers an available desk over leaving waiters without a ticket.
        $this->assertNotNull($result->queue_ticket_uuid);
        $this->assertSame(CareQueueContexts::ROUTING_ROUTED, $result->queue_routing_status);
        $this->assertDatabaseHas('care_queue_tickets', [
            'uuid' => $result->queue_ticket_uuid,
            'status' => CareQueueTicket::STATUS_WAITING,
        ]);
        Http::assertNothingSent();
    }

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

        $patient = Patient::create([
            'uuid' => (string) Str::uuid(),
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-3',
            'first_name' => 'Cara',
            'last_name' => 'Clinic',
        ]);

        $appointment = app(AppointmentService::class)->walkIn(
            $this->organization,
            $this->owner->public_id,
            [
                'branch_id' => $this->branch->id,
                'patient_id' => $patient->id,
                'practitioner_id' => $this->doctorB->id,
                'reason' => 'Fever',
            ],
            $this->owner->public_id,
        );

        $point = CareServicePoint::query()
            ->where('organization_id', $this->organization->id)
            ->where('kind', 'practitioner')
            ->where('ref_id', $this->doctorB->id)
            ->first();

        $this->assertSame($this->doctorB->id, $appointment->practitioner_id);
        $this->assertSame('C001', $appointment->queue_ticket_number);
        $this->assertSame('Consultation Room 5', $appointment->queue_destination);
        $this->assertNotNull($point);
        $this->assertSame($point->uuid, $appointment->queue_service_point_uuid);
        Http::assertNothingSent();
    }

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

        $pharmacist = Member::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'user_ref' => 'pharm-1',
            'role' => 'pharmacist',
            'branch_id' => $this->branch->id,
        ]);

        app(CareQueueProvisioner::class)->ensure(
            $this->organization->fresh(),
            CareQueueContexts::PHARMACY,
            $this->branch->id,
        );

        $queue = CareServiceQueue::query()
            ->where('organization_id', $this->organization->id)
            ->where('context', CareQueueContexts::PHARMACY)
            ->where('branch_id', $this->branch->id)
            ->firstOrFail();

        $point = CareServicePoint::query()
            ->where('service_queue_id', $queue->id)
            ->where('kind', 'member')
            ->where('ref_id', $pharmacist->id)
            ->firstOrFail();

        CareQueueTicket::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'service_queue_id' => $queue->id,
            'service_point_id' => $point->id,
            'ticket_number' => 'P001',
            'status' => CareQueueTicket::STATUS_WAITING,
            'priority' => 'walk_in',
            'customer_name' => 'Pharmacy Patient',
        ]);

        $result = app(CareQueueBridge::class)->callNext(
            $this->organization->fresh(),
            CareQueueContexts::PHARMACY,
            $this->branch->id,
            $pharmacist,
        );

        $this->assertSame('P001', $result['ticket']['ticket_number'] ?? null);
        $this->assertSame('called', $result['ticket']['status'] ?? null);
        $this->assertSame($point->uuid, $result['ticket']['assigned_counter']['uuid'] ?? null);
        Http::assertNothingSent();
    }

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

        Http::fake();

        $patient = Patient::create([
            'uuid' => (string) Str::uuid(),
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-4',
            'first_name' => 'Dee',
            'last_name' => 'Demo',
        ]);

        $appointment = Appointment::create([
            'uuid' => (string) Str::uuid(),
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'practitioner_id' => $this->doctorA->id,
            'type' => Appointment::TYPE_SCHEDULED,
            'status' => Appointment::STATUS_SCHEDULED,
            'scheduled_at' => now()->addHour(),
        ]);

        $result = app(AppointmentService::class)->checkIn($appointment, $this->owner->public_id);

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

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

        app(CareQueueProvisioner::class)->provisionOrganization($this->organization->fresh());
        $first = data_get($this->organization->fresh()->settings, 'department_queue_provisioning.consultation.queues.0.points');
        $this->assertIsArray($first);
        $this->assertGreaterThanOrEqual(2, count($first));

        $this->assertDatabaseHas('care_service_queues', [
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'context' => CareQueueContexts::CONSULTATION,
        ]);
        $this->assertEquals(
            2,
            CareServicePoint::query()
                ->where('organization_id', $this->organization->id)
                ->where('kind', 'practitioner')
                ->whereIn('ref_id', [$this->doctorA->id, $this->doctorB->id])
                ->count(),
        );

        app(CareQueueProvisioner::class)->provisionOrganization($this->organization->fresh());
        $second = data_get($this->organization->fresh()->settings, 'department_queue_provisioning.consultation.queues.0.points');
        $this->assertCount(count($first), $second);
        Http::assertNothingSent();
    }

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

        $department = Department::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'name' => 'Dentistry',
            'type' => 'dental',
            'is_active' => true,
        ]);

        $dentistA = Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'department_id' => $department->id,
            'name' => 'Dr. Dental A',
            'specialty' => 'Dentistry',
            'room' => 'Bay 1',
            'is_active' => true,
        ]);

        $dentistB = Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'department_id' => $department->id,
            'name' => 'Dr. Dental B',
            'specialty' => 'Dentistry',
            'room' => 'Bay 2',
            'is_active' => true,
        ]);

        $settings = $this->organization->settings;
        $settings['specialty_modules'] = ['dentistry' => true];
        $settings['specialty_module_provisioning'] = [
            'dentistry' => [
                'active' => true,
                'department_ids' => [$department->id],
                'queues' => [[
                    'module' => 'dentistry',
                    'branch_id' => $this->branch->id,
                    'branch_name' => 'Main',
                    'name' => 'Dentistry',
                    'prefix' => 'DEN',
                    'active' => true,
                    'synced' => false,
                ]],
            ],
        ];
        $this->organization->update(['settings' => $settings]);

        app(CareQueueProvisioner::class)->ensure(
            $this->organization->fresh(),
            'dentistry',
            $this->branch->id,
        );

        $queue = CareServiceQueue::query()
            ->where('organization_id', $this->organization->id)
            ->where('context', 'dentistry')
            ->where('branch_id', $this->branch->id)
            ->firstOrFail();

        $pointA = CareServicePoint::query()
            ->where('service_queue_id', $queue->id)
            ->where('kind', 'practitioner')
            ->where('ref_id', $dentistA->id)
            ->firstOrFail();

        $pointB = CareServicePoint::query()
            ->where('service_queue_id', $queue->id)
            ->where('kind', 'practitioner')
            ->where('ref_id', $dentistB->id)
            ->firstOrFail();

        // Ticket waits only at Bay 2; admin default desk is Bay 1 (lower ref_id).
        CareQueueTicket::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'service_queue_id' => $queue->id,
            'service_point_id' => $pointB->id,
            'ticket_number' => 'DEN001',
            'status' => CareQueueTicket::STATUS_WAITING,
            'priority' => 'walk_in',
            'customer_name' => 'Waiting Dental Patient',
        ]);

        $admin = Member::query()
            ->where('organization_id', $this->organization->id)
            ->where('role', 'hospital_admin')
            ->firstOrFail();

        $result = app(CareQueueBridge::class)->callNext(
            $this->organization->fresh(),
            'dentistry',
            $this->branch->id,
            $admin,
            null,
        );

        $this->assertSame('DEN001', $result['ticket']['ticket_number'] ?? null);
        $this->assertSame('called', $result['ticket']['status'] ?? null);
        $this->assertSame($pointB->uuid, $result['ticket']['assigned_counter']['uuid'] ?? null);
        $this->assertNotSame($pointA->uuid, $result['ticket']['assigned_counter']['uuid'] ?? null);
        Http::assertNothingSent();
    }
}
