<?php

namespace Tests\Feature;

use App\Http\Middleware\EnsurePlatformSession;
use App\Models\Appointment;
use App\Models\Branch;
use App\Models\FacilityWorkflow;
use App\Models\Member;
use App\Models\Organization;
use App\Models\Patient;
use App\Models\User;
use App\Models\Visit;
use App\Models\VisitStageAdvance;
use App\Models\WorkflowStage;
use App\Services\Care\AppointmentService;
use App\Services\Care\CareFeatures;
use App\Services\Care\CareQueueContexts;
use App\Services\Care\Workflow\WorkflowQueueGate;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;

class CareQueueBoardPerformanceTest extends TestCase
{
    use RefreshDatabase;

    protected User $owner;

    protected Organization $organization;

    protected Branch $branch;

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

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

        $this->organization = Organization::create([
            'owner_ref' => $this->owner->public_id,
            'name' => 'Perf Clinic',
            'slug' => 'perf-clinic',
            'settings' => [
                'onboarded' => true,
                'facility_type' => 'clinic',
                'plan' => 'pro',
                'plan_expires_at' => now()->addMonth()->toIso8601String(),
                'queue_integration_enabled' => true,
                'rollout' => [
                    CareFeatures::WORKFLOW_ENGINE => true,
                    CareFeatures::FINANCIAL_GATES => true,
                ],
            ],
        ]);

        // Patient queue board is reserved for clinical staff (receptionists are 403'd).
        Member::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'user_ref' => $this->owner->public_id,
            'role' => 'doctor',
        ]);

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

    public function test_queue_index_does_not_backfill_tickets_or_start_workflows(): void
    {
        $workflow = FacilityWorkflow::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'name' => 'Default',
            'is_active' => true,
        ]);
        WorkflowStage::create([
            'owner_ref' => $this->owner->public_id,
            'workflow_id' => $workflow->id,
            'code' => 'consultation',
            'name' => 'Consultation',
            'type' => 'consultation',
            'queue_context' => CareQueueContexts::CONSULTATION,
            'sort_order' => 1,
            'requires_payment' => false,
        ]);

        $seedWaiters = function (int $count, int $offset = 0): void {
            for ($i = 0; $i < $count; $i++) {
                $n = $offset + $i;
                $patient = Patient::create([
                    'owner_ref' => $this->owner->public_id,
                    'organization_id' => $this->organization->id,
                    'branch_id' => $this->branch->id,
                    'patient_number' => 'P-PERF-'.$n,
                    'first_name' => 'Pat',
                    'last_name' => (string) $n,
                ]);
                $visit = Visit::create([
                    'owner_ref' => $this->owner->public_id,
                    'organization_id' => $this->organization->id,
                    'branch_id' => $this->branch->id,
                    'patient_id' => $patient->id,
                    'status' => Visit::STATUS_OPEN,
                    'checked_in_at' => now(),
                ]);
                Appointment::create([
                    'owner_ref' => $this->owner->public_id,
                    'organization_id' => $this->organization->id,
                    'branch_id' => $this->branch->id,
                    'patient_id' => $patient->id,
                    'visit_id' => $visit->id,
                    'type' => Appointment::TYPE_WALK_IN,
                    'status' => Appointment::STATUS_WAITING,
                    'scheduled_at' => now(),
                    'waiting_at' => now()->subMinutes(100 - $n),
                    'queue_position' => $n + 1,
                ]);
            }
        };

        $seedWaiters(5);
        DB::flushQueryLog();
        DB::enableQueryLog();
        $this->actingAs($this->owner)
            ->get(route('care.queue.index', ['branch_id' => $this->branch->id]))
            ->assertOk();
        $queriesWith5 = count(DB::getQueryLog());
        DB::disableQueryLog();

        $seedWaiters(20, 5);
        DB::flushQueryLog();
        DB::enableQueryLog();
        $this->actingAs($this->owner)
            ->get(route('care.queue.index', ['branch_id' => $this->branch->id]))
            ->assertOk()
            ->assertSee('Patient flow');
        $log = collect(DB::getQueryLog());
        DB::disableQueryLog();
        $queriesWith25 = $log->count();

        $this->assertSame(0, Appointment::query()->whereNotNull('queue_ticket_uuid')->where('queue_ticket_uuid', '!=', '')->count());
        $this->assertSame(0, VisitStageAdvance::query()->count(), 'Board GET must not start workflows');

        // Adding 20 waiters must not add ~N queries (gate refresh / ticket sync / context loops).
        $delta = $queriesWith25 - $queriesWith5;
        $this->assertLessThan(
            25,
            $delta,
            "Query delta for +20 waiters should be small (eager loads), got {$delta} (5={$queriesWith5}, 25={$queriesWith25})",
        );

        $advanceLookups = $log->filter(
            fn ($q) => str_contains(strtolower($q['query']), 'care_visit_stage_advances'),
        )->count();
        $this->assertLessThanOrEqual(
            3,
            $advanceLookups,
            "visit_stage_advances lookups should be eager-loaded, got {$advanceLookups}",
        );
    }

    public function test_queue_board_caps_waiting_list_size(): void
    {
        $limit = AppointmentService::QUEUE_BOARD_LIMIT;

        for ($i = 0; $i < $limit + 15; $i++) {
            $patient = Patient::create([
                'owner_ref' => $this->owner->public_id,
                'organization_id' => $this->organization->id,
                'branch_id' => $this->branch->id,
                'patient_number' => 'P-CAP-'.$i,
                'first_name' => 'Cap',
                'last_name' => (string) $i,
            ]);
            Appointment::create([
                'owner_ref' => $this->owner->public_id,
                'organization_id' => $this->organization->id,
                'branch_id' => $this->branch->id,
                'patient_id' => $patient->id,
                'type' => Appointment::TYPE_WALK_IN,
                'status' => Appointment::STATUS_WAITING,
                'scheduled_at' => now(),
                'waiting_at' => now()->subMinutes($limit + 15 - $i),
                'queue_position' => $i + 1,
            ]);
        }

        $queue = app(AppointmentService::class)->queue(
            $this->owner->public_id,
            $this->branch->id,
        );

        $this->assertCount($limit, $queue);
        $this->assertSame(1, (int) $queue->first()->queue_position);
    }

    public function test_display_gate_hides_blocked_visits_without_refresh(): void
    {
        $workflow = FacilityWorkflow::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'name' => 'Gated',
            'is_active' => true,
        ]);
        $stage = WorkflowStage::create([
            'owner_ref' => $this->owner->public_id,
            'workflow_id' => $workflow->id,
            'code' => 'consultation',
            'name' => 'Consultation',
            'type' => 'consultation',
            'queue_context' => CareQueueContexts::CONSULTATION,
            'sort_order' => 1,
            'requires_payment' => true,
            'payment_timing' => 'before',
            'default_amount_minor' => 1000,
        ]);

        $patient = Patient::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-GATE',
            'first_name' => 'Blocked',
            'last_name' => 'Patient',
        ]);
        $visit = Visit::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'status' => Visit::STATUS_OPEN,
            'checked_in_at' => now(),
        ]);
        VisitStageAdvance::create([
            'owner_ref' => $this->owner->public_id,
            'visit_id' => $visit->id,
            'workflow_id' => $workflow->id,
            'workflow_stage_id' => $stage->id,
            'stage_code' => 'consultation',
            'status' => VisitStageAdvance::STATUS_BLOCKED,
            'blocked_reason' => 'awaiting_payment',
            'entered_at' => now(),
        ]);

        $gate = app(WorkflowQueueGate::class);
        $this->assertFalse($gate->isReleasedForDisplay(
            $this->organization,
            $visit->fresh('currentStageAdvance.stage'),
            CareQueueContexts::CONSULTATION,
        ));
    }
}
