<?php

namespace Tests\Feature;

use App\Http\Middleware\EnsurePlatformSession;
use App\Models\Appointment;
use App\Models\Branch;
use App\Models\Consultation;
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\SpecialtyModuleService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;

class CareSpecialtyAccessTest extends TestCase
{
    use RefreshDatabase;

    protected User $owner;

    protected Organization $organization;

    protected Branch $branch;

    protected SpecialtyModuleService $modules;

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

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

        $this->organization = Organization::create([
            'owner_ref' => $this->owner->public_id,
            'name' => 'Access Clinic',
            'slug' => 'access-clinic',
            'settings' => [
                'onboarded' => true,
                'facility_type' => 'clinic',
                '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->modules = app(SpecialtyModuleService::class);
        $this->modules->ensureDefaultModulesProvisioned($this->organization, $this->owner->public_id);
        foreach (['cardiology', 'infusion', 'pathology', 'dentistry', 'psychiatry', 'surgery', 'radiology'] as $key) {
            $this->modules->activate($this->organization->fresh(), $this->owner->public_id, $key);
        }
        $this->organization->refresh();
    }

    protected function makeStaff(string $role, string $suffix): array
    {
        $user = User::create([
            'public_id' => 'access-'.$suffix,
            'name' => ucfirst($role).' '.$suffix,
            'email' => $suffix.'@example.com',
        ]);
        $member = Member::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'user_ref' => $user->public_id,
            'role' => $role,
            'branch_id' => $this->branch->id,
        ]);

        return [$user, $member];
    }

    public function test_nurse_and_pharmacist_manage_general_modules(): void
    {
        foreach (['vaccination', 'child_welfare', 'womens_health'] as $key) {
            $this->modules->activate($this->organization->fresh(), $this->owner->public_id, $key);
        }
        $this->organization->refresh();

        [, $nurse] = $this->makeStaff('nurse', 'nurse1');
        [, $edNurse] = $this->makeStaff('ed_nurse', 'edn1');
        [, $pharmacist] = $this->makeStaff('pharmacist', 'rx1');
        [, $lab] = $this->makeStaff('lab_technician', 'lab1');
        [, $receptionist] = $this->makeStaff('receptionist', 'rec1');

        // Legacy floor nurse: vaccination / CWC / infusion / Women's Health manage; emergency refer only.
        $this->assertFalse($this->modules->memberCanManage($this->organization, $nurse, 'emergency'));
        $this->assertTrue($this->modules->memberCanRefer($this->organization, $nurse, 'emergency'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $nurse, 'vaccination'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $nurse, 'infusion'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $nurse, 'womens_health'));
        // ED nurse matrix apps: Emergency, Blood Bank, Radiology, Pathology.
        $this->assertTrue($this->modules->memberCanManage($this->organization, $edNurse, 'emergency'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $edNurse, 'pathology'));
        // Pharmacist: no specialty manage; refer/view pharmacy-adjacent modules.
        $this->assertFalse($this->modules->memberCanManage($this->organization, $pharmacist, 'emergency'));
        $this->assertFalse($this->modules->memberCanManage($this->organization, $pharmacist, 'infusion'));
        $this->assertTrue($this->modules->memberCanRefer($this->organization, $pharmacist, 'infusion'));
        $this->assertTrue($this->modules->memberCanView($this->organization, $pharmacist, 'emergency'));
        $this->assertFalse($this->modules->memberCanManage($this->organization, $pharmacist, 'cardiology'));
        // Lab: pathology + blood bank manage; radiology view.
        $this->assertTrue($this->modules->memberCanManage($this->organization, $lab, 'pathology'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $lab, 'blood_bank'));
        $this->assertSame('view', $this->modules->memberAccessLevel($this->organization, $lab, 'radiology'));
        // Receptionist: refer only (no specialty manage).
        $this->assertFalse($this->modules->memberCanManage($this->organization, $receptionist, 'emergency'));
        $this->assertTrue($this->modules->memberCanRefer($this->organization, $receptionist, 'emergency'));
        $this->assertTrue($this->modules->memberCanRefer($this->organization, $receptionist, 'dentistry'));
    }

    public function test_gp_doctor_manages_general_but_only_views_restricted(): void
    {
        [$doctor, $member] = $this->makeStaff('general_physician', 'gp1');
        Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'member_id' => $member->id,
            'user_ref' => $doctor->public_id,
            'name' => 'Dr GP',
            'specialty' => 'General Practice',
            'is_active' => true,
        ]);

        $this->assertFalse($this->modules->isDeskSpecialist($this->organization, $member));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'emergency'));
        // Dentistry is not a GP primary app — refer only.
        $this->assertFalse($this->modules->memberCanManage($this->organization, $member, 'dentistry'));
        $this->assertTrue($this->modules->memberCanRefer($this->organization, $member, 'dentistry'));
        // Cardiology / ophthalmology are GP primary apps (manage).
        $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'cardiology'));
        $this->modules->activate($this->organization->fresh(), $this->owner->public_id, 'ophthalmology');
        $this->organization->refresh();
        $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'ophthalmology'));
        // Psychiatry is outside GP manage — refer/view via roleReferApps.
        $this->assertFalse($this->modules->memberCanManage($this->organization, $member, 'psychiatry'));
        $this->assertTrue($this->modules->memberCanView($this->organization, $member, 'psychiatry'));
        $this->assertTrue($this->modules->memberCanRefer($this->organization, $member, 'psychiatry'));
        $this->assertSame('refer', $this->modules->memberAccessLevel($this->organization, $member, 'psychiatry'));
    }

    public function test_dedicated_cardiologist_role_manages_cardiology_only(): void
    {
        [, $member] = $this->makeStaff('cardiologist', 'cardio-role');

        $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'cardiology'));
        $this->assertSame('manage', $this->modules->memberAccessLevel($this->organization, $member, 'cardiology'));
        $this->assertSame('none', $this->modules->memberAccessLevel($this->organization, $member, 'emergency'));
        $this->assertSame('none', $this->modules->memberAccessLevel($this->organization, $member, 'dentistry'));
        $this->assertTrue(app(\App\Services\Care\CarePermissions::class)->isSingleAppSpecialist($member));
        $this->assertFalse($this->modules->shouldShowSpecialtyNav($this->organization, $member));
    }

    public function test_cardiologist_manages_restricted_cardiology(): void
    {
        [$doctor, $member] = $this->makeStaff('doctor', 'cardio1');
        $dept = Department::query()->where('type', 'cardiology')->firstOrFail();
        Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'department_id' => $dept->id,
            'member_id' => $member->id,
            'user_ref' => $doctor->public_id,
            'name' => 'Dr Cardio',
            'specialty' => 'Cardiology',
            'is_active' => true,
        ]);

        $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'cardiology'));
        $this->assertSame('manage', $this->modules->memberAccessLevel($this->organization, $member, 'cardiology'));
        // Desk specialists do not keep GP-wide general/limited manage.
        $this->assertSame('none', $this->modules->memberAccessLevel($this->organization, $member, 'emergency'));
        $this->assertSame('none', $this->modules->memberAccessLevel($this->organization, $member, 'dentistry'));
    }

    public function test_dentistry_specialist_only_sees_matching_modules(): void
    {
        [$doctor, $member] = $this->makeStaff('doctor', 'dentist1');
        Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'member_id' => $member->id,
            'user_ref' => $doctor->public_id,
            'name' => 'Dr Dentist',
            'specialty' => 'Dentistry',
            'is_active' => true,
        ]);

        $this->assertTrue($this->modules->isDeskSpecialist($this->organization, $member));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'dentistry'));
        $this->assertSame('manage', $this->modules->memberAccessLevel($this->organization, $member, 'dentistry'));

        foreach (['emergency', 'blood_bank', 'cardiology', 'infusion', 'pathology'] as $key) {
            $this->assertSame(
                'none',
                $this->modules->memberAccessLevel($this->organization, $member, $key),
                "Dentistry specialist should not see [{$key}]",
            );
            $this->assertFalse($this->modules->memberCanManage($this->organization, $member, $key));
            $this->assertFalse($this->modules->memberCanView($this->organization, $member, $key));
            $this->assertFalse($this->modules->memberCanRefer($this->organization, $member, $key));
        }

        $enabledKeys = collect($this->modules->enabledModulesForMember($this->organization, $member))
            ->pluck('key')
            ->all();
        $this->assertContains('dentistry', $enabledKeys);
        $this->assertNotContains('emergency', $enabledKeys);
        $this->assertNotContains('blood_bank', $enabledKeys);
        $this->assertNotContains('cardiology', $enabledKeys);

        // Single-desk specialists: no Specialty sidebar group (dashboard cards remain).
        $this->assertFalse($this->modules->shouldShowSpecialtyNav($this->organization, $member));

        $this->actingAs($doctor)
            ->get(route('care.dashboard'))
            ->assertOk()
            ->assertSee('Dentistry')
            ->assertSee('Specialty modules')
            ->assertDontSee('>Specialty</p>', false)
            ->assertDontSee('Emergency')
            ->assertDontSee('Cardiology')
            ->assertDontSee('Blood Bank');
    }

    public function test_gp_keeps_specialty_sidebar_nav(): void
    {
        [$doctor, $member] = $this->makeStaff('doctor', 'gp-nav');
        Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'member_id' => $member->id,
            'user_ref' => $doctor->public_id,
            'name' => 'Dr GP Nav',
            'specialty' => 'General Practice',
            'is_active' => true,
        ]);

        $this->assertFalse($this->modules->isDeskSpecialist($this->organization, $member));
        $this->assertTrue($this->modules->shouldShowSpecialtyNav($this->organization, $member));

        $this->actingAs($doctor)
            ->get(route('care.dashboard'))
            ->assertOk()
            ->assertSee('>Specialty</p>', false)
            ->assertSee('Emergency')
            ->assertSee('Cardiology')
            ->assertSee('Dentistry'); // GP refer access
    }

    public function test_dentistry_specialty_does_not_match_ent_keyword(): void
    {
        [$doctor, $member] = $this->makeStaff('doctor', 'dentist-ent-trap');
        Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'member_id' => $member->id,
            'user_ref' => $doctor->public_id,
            'name' => 'Dr Dentist',
            'specialty' => 'Dentistry',
            'is_active' => true,
        ]);

        $this->assertTrue($this->modules->specialistBelongsToModule($this->organization, $member, 'dentistry'));
        $this->assertFalse($this->modules->specialistBelongsToModule($this->organization, $member, 'ent'));
        $this->assertSame('none', $this->modules->memberAccessLevel($this->organization, $member, 'ent'));
    }

    public function test_gp_can_refer_into_restricted_specialty_queue(): void
    {
        [$doctor, $member] = $this->makeStaff('doctor', 'gp2');
        Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'member_id' => $member->id,
            'user_ref' => $doctor->public_id,
            'name' => 'Dr GP2',
            'specialty' => 'General Practice',
            'is_active' => true,
        ]);

        $patient = Patient::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-REF-1',
            'first_name' => 'Ama',
            'last_name' => 'Refer',
            'gender' => 'female',
            'date_of_birth' => '1990-01-01',
        ]);

        $this->actingAs($doctor)
            ->post(route('care.specialty.refer', 'cardiology'), [
                'patient_id' => $patient->id,
                'branch_id' => $this->branch->id,
                'reason' => 'Chest pain review',
            ])
            ->assertRedirect(route('care.specialty.show', 'cardiology'));

        $this->assertDatabaseHas('care_appointments', [
            'patient_id' => $patient->id,
            'reason' => 'Chest pain review',
            'status' => Appointment::STATUS_WAITING,
        ]);
    }

    public function test_gp_cannot_save_clinical_on_restricted_module(): void
    {
        [$doctor, $member] = $this->makeStaff('general_physician', 'gp3');
        Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'member_id' => $member->id,
            'user_ref' => $doctor->public_id,
            'name' => 'Dr GP3',
            'specialty' => 'General Practice',
            'is_active' => true,
        ]);

        $dept = Department::query()->where('type', 'psychiatry')->firstOrFail();
        $patient = Patient::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-PSY-1',
            'first_name' => 'Kofi',
            'last_name' => 'Mind',
            'gender' => 'male',
            'date_of_birth' => '1980-01-01',
        ]);
        $visit = \App\Models\Visit::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'status' => \App\Models\Visit::STATUS_IN_PROGRESS,
            '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,
            'department_id' => $dept->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_IN_CONSULTATION,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'started_at' => now(),
        ]);

        $this->actingAs($doctor)
            ->get(route('care.specialty.workspace', ['module' => 'psychiatry', 'visit' => $visit, 'tab' => 'exam']))
            ->assertOk()
            ->assertSee('view + refer')
            ->assertDontSee('Save record')
            ->assertDontSee('Call next')
            ->assertDontSee('Add to invoice')
            ->assertDontSee('Upload document')
            ->assertSee('Refer to specialty queue')
            ->assertDontSee(
                'action="'.route('care.specialty.consultation.start', ['module' => 'psychiatry', 'visit' => $visit], false).'"',
                false,
            );

        $this->actingAs($doctor)
            ->post(route('care.specialty.clinical.save', ['module' => 'psychiatry', 'visit' => $visit]), [
                'tab' => 'exam',
                'payload' => [
                    'chief_complaint' => 'Should not save',
                ],
            ])
            ->assertForbidden();
    }

    public function test_sidebar_and_dashboard_exclude_modules_with_no_access(): void
    {
        [$pharmacistUser, $pharmacistMember] = $this->makeStaff('pharmacist', 'rx-nav');
        [, $epMember] = $this->makeStaff('emergency_physician', 'ep-nav');

        $enabledForPharmacist = collect($this->modules->enabledModulesForMember($this->organization, $pharmacistMember))
            ->pluck('key')
            ->all();
        $enabledForEp = collect($this->modules->enabledModulesForMember($this->organization, $epMember))
            ->pluck('key')
            ->all();

        // Pharmacist: pharmacy-adjacent specialty refer/view (multi-app → Specialty nav).
        $this->assertContains('emergency', $enabledForPharmacist);
        $this->assertContains('infusion', $enabledForPharmacist);
        $this->assertNotContains('cardiology', $enabledForPharmacist);
        $this->assertTrue($this->modules->shouldShowSpecialtyNav($this->organization, $pharmacistMember));

        $this->assertContains('emergency', $enabledForEp);
        $this->assertContains('radiology', $enabledForEp);
        $this->assertNotContains('dentistry', $enabledForEp);
        $this->assertTrue($this->modules->shouldShowSpecialtyNav($this->organization, $epMember));

        $this->actingAs($pharmacistUser)
            ->get(route('care.dashboard'))
            ->assertOk()
            ->assertSee('>Specialty</p>', false);
    }

    public function test_manage_role_still_sees_mutate_actions_on_restricted_module(): void
    {
        [$doctor, $member] = $this->makeStaff('doctor', 'cardio-ui');
        $dept = Department::query()->where('type', 'cardiology')->firstOrFail();
        Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'department_id' => $dept->id,
            'member_id' => $member->id,
            'user_ref' => $doctor->public_id,
            'name' => 'Dr Cardio UI',
            'specialty' => 'Cardiology',
            'is_active' => true,
        ]);

        $patient = Patient::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-CARD-UI',
            'first_name' => 'Abena',
            'last_name' => 'Heart',
            'gender' => 'female',
            'date_of_birth' => '1985-01-01',
        ]);
        $visit = \App\Models\Visit::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'status' => \App\Models\Visit::STATUS_IN_PROGRESS,
            '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,
            'department_id' => $dept->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_WAITING,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
        ]);

        $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'cardiology'));

        $this->actingAs($doctor)
            ->get(route('care.specialty.workspace', ['module' => 'cardiology', 'visit' => $visit]))
            ->assertOk()
            ->assertDontSee('view + refer')
            ->assertSee('Start');
    }

    public function test_admin_keeps_full_manage_access(): void
    {
        $admin = Member::query()->where('user_ref', $this->owner->public_id)->firstOrFail();
        $this->assertTrue($this->modules->memberCanManage($this->organization, $admin, 'cardiology'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $admin, 'emergency'));
    }

    /**
     * @return array{0: \App\Models\User, 1: Member, 2: \App\Models\Visit}
     */
    protected function specialtyVisitForRole(string $role, string $module, string $suffix): array
    {
        if ($module === 'dentistry') {
            return $this->dentistryVisitForRole($role, $suffix);
        }
        if ($module === 'emergency') {
            return $this->emergencyVisitForRole($role, $suffix);
        }

        [$user, $member] = $this->makeStaff($role, $suffix);
        $definition = $this->modules->definition($module);
        $deptType = (string) ($definition['department_type'] ?? $module);
        $dept = Department::query()->where('type', $deptType)->firstOrFail();
        $patient = Patient::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-'.strtoupper(substr($module, 0, 3)).'-'.$suffix,
            'first_name' => 'Floor',
            'last_name' => ucfirst($module),
            'gender' => 'female',
            'date_of_birth' => '1990-01-01',
        ]);
        $visit = \App\Models\Visit::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'status' => \App\Models\Visit::STATUS_IN_PROGRESS,
            'checked_in_at' => now(),
            'specialty_stage' => 'waiting',
        ]);
        Appointment::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'department_id' => $dept->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_WAITING,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'waiting_at' => now(),
        ]);

        return [$user, $member, $visit];
    }

    /**
     * @return array{0: \App\Models\User, 1: Member, 2: \App\Models\Visit}
     */
    protected function dentistryVisitForRole(string $role, string $suffix): array
    {
        [$user, $member] = $this->makeStaff($role, $suffix);
        if (in_array($role, ['doctor', 'dentist'], true)) {
            Practitioner::create([
                'owner_ref' => $this->owner->public_id,
                'organization_id' => $this->organization->id,
                'branch_id' => $this->branch->id,
                'member_id' => $member->id,
                'user_ref' => $user->public_id,
                'name' => 'Dr '.$suffix,
                'specialty' => 'Dentistry',
                'is_active' => true,
            ]);
        }

        $dept = Department::query()->where('type', 'dental')->firstOrFail();
        $patient = Patient::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-DEN-'.$suffix,
            'first_name' => 'Efua',
            'last_name' => 'Boateng',
            'gender' => 'female',
            'date_of_birth' => '1992-06-01',
        ]);
        $visit = \App\Models\Visit::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'status' => \App\Models\Visit::STATUS_IN_PROGRESS,
            'checked_in_at' => now(),
            'specialty_stage' => 'waiting',
        ]);
        Appointment::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'department_id' => $dept->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_WAITING,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'waiting_at' => now(),
        ]);

        return [$user, $member, $visit];
    }

    /**
     * @return array{0: \App\Models\User, 1: Member, 2: \App\Models\Visit}
     */
    protected function emergencyVisitForRole(string $role, string $suffix): array
    {
        [$user, $member] = $this->makeStaff($role, $suffix);

        $dept = Department::query()->where('type', 'emergency')->firstOrFail();
        $patient = Patient::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-ER-'.$suffix,
            'first_name' => 'Kojo',
            'last_name' => 'Floor',
            'gender' => 'male',
            'date_of_birth' => '1988-01-01',
        ]);
        $visit = \App\Models\Visit::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'status' => \App\Models\Visit::STATUS_IN_PROGRESS,
            'checked_in_at' => now(),
            'specialty_stage' => 'arrival',
        ]);
        Appointment::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'department_id' => $dept->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_WAITING,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'waiting_at' => now(),
        ]);

        return [$user, $member, $visit];
    }

    /**
     * Roles that can manage a specialty module but lack consultations.manage must not see
     * Start / stage / chart-mutate CTAs. Gating is ability-based — not nurse-hardcoded.
     *
     * @return list<array{0: string, 1: string, 2: string}>
     */
    public static function floorRolesWithoutConsultManage(): array
    {
        return [
            ['ed_nurse', 'emergency', 'floor-edn-er'],
            ['theatre_nurse', 'surgery', 'floor-tn-sur'],
            ['lab_technician', 'pathology', 'floor-lab-path'],
            ['radiographer', 'radiology', 'floor-rad-tech'],
        ];
    }

    #[DataProvider('floorRolesWithoutConsultManage')]
    public function test_floor_roles_without_consult_do_not_see_mutate_ctas(
        string $role,
        string $module,
        string $suffix,
    ): void {
        [$user, $member, $visit] = $this->specialtyVisitForRole($role, $module, $suffix);

        $permissions = app(\App\Services\Care\CarePermissions::class);
        $this->assertTrue($this->modules->memberCanManage($this->organization, $member, $module));
        $this->assertFalse($permissions->can($member, 'consultations.manage'));
        // Prove gating is not nurse-only: same CTA rules for every floor role without consult.
        $this->assertNotSame('hospital_admin', $member->role);

        $html = $this->actingAs($user)
            ->get(route('care.specialty.workspace', ['module' => $module, 'visit' => $visit, 'tab' => 'overview']))
            ->assertOk()
            ->assertSee('consultation access')
            ->assertDontSee('Seat at chair')
            ->assertDontSee('Start triage')
            ->assertSee('Patient chart')
            ->getContent();

        // Nurses keep specialty workspaces but lose the patient-flow board link.
        if ($permissions->canAccessPatientQueue($member)) {
            $this->assertStringContainsString('Back to queue', $html);
            $this->assertStringNotContainsString('Specialty home', $html);
        } else {
            $this->assertStringNotContainsString('Back to queue', $html);
            $this->assertStringContainsString('Specialty home', $html);
        }

        $this->assertStringNotContainsString(
            route('care.specialty.consultation.start', ['module' => $module, 'visit' => $visit], absolute: false),
            $html,
        );

        if ($module === 'dentistry') {
            $this->assertStringNotContainsString(
                route('care.specialty.dentistry.stage', $visit, absolute: false),
                $html,
            );
            $this->assertStringContainsString('Waiting', $html);
        } elseif ($module === 'emergency') {
            $this->assertStringNotContainsString(
                route('care.specialty.emergency.stage', $visit, absolute: false),
                $html,
            );
        }

        if ($permissions->can($member, 'appointments.manage')) {
            $this->assertStringContainsString('Register / book', $html);
        } else {
            $this->assertStringNotContainsString('Register / book', $html);
        }
    }

    public function test_nurse_dentistry_mutate_posts_are_forbidden(): void
    {
        [$nurseUser, , $visit] = $this->dentistryVisitForRole('nurse', 'den-nurse-post');

        $this->actingAs($nurseUser)
            ->post(route('care.specialty.dentistry.stage', $visit), ['stage' => 'chair'])
            ->assertForbidden();

        $this->actingAs($nurseUser)
            ->post(route('care.specialty.consultation.start', ['module' => 'dentistry', 'visit' => $visit]))
            ->assertForbidden();

        $this->actingAs($nurseUser)
            ->post(route('care.specialty.dentistry.tooth', $visit), [
                'tooth_code' => '16',
                'status' => 'present',
            ])
            ->assertForbidden();
    }

    public function test_receptionist_dentistry_mutate_posts_are_forbidden(): void
    {
        [$user, , $visit] = $this->dentistryVisitForRole('receptionist', 'den-recv-post');

        $this->actingAs($user)
            ->post(route('care.specialty.dentistry.stage', $visit), ['stage' => 'chair'])
            ->assertForbidden();

        $this->actingAs($user)
            ->post(route('care.specialty.consultation.start', ['module' => 'dentistry', 'visit' => $visit]))
            ->assertForbidden();
    }

    public function test_lab_technician_and_pharmacist_emergency_mutate_posts_are_forbidden(): void
    {
        foreach (['lab_technician' => 'lab-er-post', 'pharmacist' => 'rx-er-post', 'nurse' => 'nurse-er-post'] as $role => $suffix) {
            [$user, , $visit] = $this->emergencyVisitForRole($role, $suffix);

            $this->actingAs($user)
                ->post(route('care.specialty.emergency.stage', $visit), ['stage' => 'treatment'])
                ->assertForbidden();

            $this->actingAs($user)
                ->post(route('care.specialty.consultation.start', ['module' => 'emergency', 'visit' => $visit]))
                ->assertForbidden();
        }
    }

    public function test_ed_nurse_cannot_discharge_emergency_visit(): void
    {
        [$user, $member, $visit] = $this->emergencyVisitForRole('ed_nurse', 'edn-discharge');
        $permissions = app(\App\Services\Care\CarePermissions::class);
        $this->assertTrue($this->modules->memberCanManage($this->organization, $member, 'emergency'));
        $this->assertFalse($permissions->can($member, 'emergency.discharge'));

        $this->actingAs($user)
            ->post(route('care.specialty.emergency.disposition', $visit), [
                'payload' => [
                    'disposition' => 'Discharged home',
                    'destination' => 'Home',
                    'medications' => 'None',
                ],
            ])
            ->assertForbidden();
    }

    public function test_rbac_matrix_smoke_key_roles(): void
    {
        $permissions = app(\App\Services\Care\CarePermissions::class);

        [, $ep] = $this->makeStaff('emergency_physician', 'matrix-ep');
        [, $dentist] = $this->makeStaff('dentist', 'matrix-den');
        [, $cashier] = $this->makeStaff('cashier', 'matrix-cash');
        [, $lab] = $this->makeStaff('lab_technician', 'matrix-lab');
        [, $labMgr] = $this->makeStaff('lab_manager', 'matrix-labm');
        [, $billing] = $this->makeStaff('billing_officer', 'matrix-bill');

        $this->assertTrue($this->modules->memberCanManage($this->organization, $ep, 'emergency'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $ep, 'radiology'));
        $this->assertFalse($this->modules->memberCanManage($this->organization, $ep, 'dentistry'));

        $this->assertTrue($this->modules->memberCanManage($this->organization, $dentist, 'dentistry'));
        $this->assertFalse($this->modules->memberCanManage($this->organization, $dentist, 'emergency'));
        $this->assertFalse($this->modules->shouldShowSpecialtyNav($this->organization, $dentist));

        $this->assertTrue($permissions->can($cashier, 'payments.manage'));
        $this->assertFalse($permissions->can($cashier, 'bills.manage'));
        $this->assertFalse($permissions->can($cashier, 'consultations.manage'));
        $this->assertSame([], $this->modules->enabledModulesForMember($this->organization, $cashier));

        $this->assertTrue($permissions->can($lab, 'lab.manage'));
        $this->assertTrue($permissions->can($lab, 'pathology.result.enter'));
        $this->assertFalse($permissions->can($lab, 'pathology.result.approve'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $lab, 'blood_bank'));
        $this->assertTrue($permissions->can($labMgr, 'pathology.result.approve'));
        $this->assertTrue($permissions->can($labMgr, 'lab.catalog.manage'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $labMgr, 'blood_bank'));

        [, $cardiologist] = $this->makeStaff('cardiologist', 'matrix-cardio');
        [, $ophthalmologist] = $this->makeStaff('ophthalmologist', 'matrix-oph');
        $this->modules->activate($this->organization->fresh(), $this->owner->public_id, 'ophthalmology');
        $this->organization->refresh();
        $this->assertTrue($this->modules->memberCanManage($this->organization, $cardiologist, 'cardiology'));
        $this->assertFalse($this->modules->memberCanManage($this->organization, $cardiologist, 'emergency'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $ophthalmologist, 'ophthalmology'));

        $this->assertTrue($permissions->can($billing, 'bills.manage'));
        $this->assertFalse($permissions->can($billing, 'payments.manage'));
    }

    public function test_doctor_on_dentistry_sees_and_can_advance_stage(): void
    {
        [$doctorUser, $doctorMember, $visit] = $this->dentistryVisitForRole('dentist', 'den-doc');

        $this->assertTrue($this->modules->memberCanManage($this->organization, $doctorMember, 'dentistry'));
        $this->assertTrue(app(\App\Services\Care\CarePermissions::class)->can($doctorMember, 'consultations.manage'));

        $this->actingAs($doctorUser)
            ->get(route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'overview']))
            ->assertOk()
            ->assertSee('Seat at chair')
            ->assertSee('Start')
            ->assertDontSee('consultation access');

        $this->actingAs($doctorUser)
            ->post(route('care.specialty.dentistry.stage', $visit), ['stage' => 'chair'])
            ->assertRedirect();

        $this->assertSame('chair', $visit->fresh()->specialty_stage);
    }

    public function test_gp_with_consult_ability_but_no_module_manage_hides_mutate_ctas(): void
    {
        [$doctor, $member] = $this->makeStaff('general_physician', 'gp-no-manage');
        Practitioner::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'member_id' => $member->id,
            'user_ref' => $doctor->public_id,
            'name' => 'Dr GP No Manage',
            'specialty' => 'General Practice',
            'is_active' => true,
        ]);

        $permissions = app(\App\Services\Care\CarePermissions::class);
        $this->assertTrue($permissions->can($member, 'consultations.manage'));
        $this->assertFalse($this->modules->memberCanManage($this->organization, $member, 'psychiatry'));
        $this->assertSame('refer', $this->modules->memberAccessLevel($this->organization, $member, 'psychiatry'));

        $dept = Department::query()->where('type', 'psychiatry')->firstOrFail();
        $patient = Patient::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-GP-NM',
            'first_name' => 'Yaw',
            'last_name' => 'ReferOnly',
            'gender' => 'male',
            'date_of_birth' => '1975-01-01',
        ]);
        $visit = \App\Models\Visit::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'status' => \App\Models\Visit::STATUS_IN_PROGRESS,
            '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,
            'department_id' => $dept->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_WAITING,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'waiting_at' => now(),
        ]);

        $html = $this->actingAs($doctor)
            ->get(route('care.specialty.workspace', ['module' => 'psychiatry', 'visit' => $visit, 'tab' => 'overview']))
            ->assertOk()
            ->assertSee('view + refer')
            ->assertDontSee('consultation access')
            ->assertDontSee('Seat at chair')
            ->assertDontSee('Start triage')
            ->getContent();

        $this->assertStringNotContainsString(
            route('care.specialty.consultation.start', ['module' => 'psychiatry', 'visit' => $visit], absolute: false),
            $html,
        );
        $this->assertStringNotContainsString('>Start</button>', $html);
    }

    public function test_admin_on_dentistry_sees_mutate_actions(): void
    {
        $dept = Department::query()->where('type', 'dental')->firstOrFail();
        $patient = Patient::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-DEN-ADMIN',
            'first_name' => 'Admin',
            'last_name' => 'Patient',
            'gender' => 'male',
            'date_of_birth' => '1980-01-01',
        ]);
        $visit = \App\Models\Visit::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'status' => \App\Models\Visit::STATUS_IN_PROGRESS,
            'checked_in_at' => now(),
            'specialty_stage' => 'waiting',
        ]);
        Appointment::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'department_id' => $dept->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_WAITING,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'waiting_at' => now(),
        ]);

        $this->actingAs($this->owner)
            ->get(route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $visit, 'tab' => 'overview']))
            ->assertOk()
            ->assertSee('Seat at chair')
            ->assertSee('Start');
    }

    public function test_nurse_can_save_emergency_vitals_with_vitals_manage(): void
    {
        [$nurseUser, $nurseMember] = $this->makeStaff('ed_nurse', 'er-vitals');
        $this->assertTrue(app(\App\Services\Care\CarePermissions::class)->can($nurseMember, 'vitals.manage'));
        $this->assertFalse(app(\App\Services\Care\CarePermissions::class)->can($nurseMember, 'consultations.manage'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $nurseMember, 'emergency'));

        $dept = Department::query()->where('type', 'emergency')->firstOrFail();
        $patient = Patient::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_number' => 'P-ER-VITALS',
            'first_name' => 'Kojo',
            'last_name' => 'Mensah',
            'gender' => 'male',
            'date_of_birth' => '1988-01-01',
        ]);
        $visit = \App\Models\Visit::create([
            'owner_ref' => $this->owner->public_id,
            'organization_id' => $this->organization->id,
            'branch_id' => $this->branch->id,
            'patient_id' => $patient->id,
            'status' => \App\Models\Visit::STATUS_IN_PROGRESS,
            '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,
            'department_id' => $dept->id,
            'visit_id' => $visit->id,
            'type' => Appointment::TYPE_WALK_IN,
            'status' => Appointment::STATUS_IN_CONSULTATION,
            'scheduled_at' => now(),
            'checked_in_at' => now(),
            'started_at' => now(),
        ]);

        $this->actingAs($nurseUser)
            ->get(route('care.specialty.workspace', ['module' => 'emergency', 'visit' => $visit, 'tab' => 'vitals']))
            ->assertOk()
            ->assertSee('Save vitals')
            ->assertDontSee('Seat at chair')
            ->assertDontSee('Start triage');

        $this->actingAs($nurseUser)
            ->post(route('care.specialty.emergency.vitals', $visit), [
                'bp_systolic' => 120,
                'bp_diastolic' => 80,
                'pulse' => 72,
            ])
            ->assertRedirect();

        $this->actingAs($nurseUser)
            ->post(route('care.specialty.emergency.stage', $visit), ['stage' => 'treatment'])
            ->assertForbidden();
    }

    public function test_doctor_with_prescriptions_manage_sees_prescribe_on_specialty_workspace(): void
    {
        [$doctorUser, $doctorMember, $visit] = $this->dentistryVisitForRole('dentist', 'den-rx');
        $appointment = $visit->appointment()->firstOrFail();
        $appointment->update([
            'status' => Appointment::STATUS_IN_CONSULTATION,
            'started_at' => now(),
            'queue_ticket_status' => 'serving',
        ]);
        $consultation = Consultation::create([
            'owner_ref' => $this->owner->public_id,
            'visit_id' => $visit->id,
            'appointment_id' => $appointment->id,
            'patient_id' => $visit->patient_id,
            'status' => Consultation::STATUS_DRAFT,
            'started_at' => now(),
        ]);

        $permissions = app(\App\Services\Care\CarePermissions::class);
        $this->assertTrue($permissions->can($doctorMember, 'prescriptions.manage'));
        $this->assertTrue($this->modules->memberCanManage($this->organization, $doctorMember, 'dentistry'));

        $html = $this->actingAs($doctorUser)
            ->get(route('care.specialty.workspace', [
                'module' => 'dentistry',
                'visit' => $visit,
                'tab' => 'overview',
            ]))
            ->assertOk()
            ->assertSee('Prescribe')
            ->assertSee('New prescription')
            ->getContent();

        $this->assertStringContainsString(
            route('care.prescriptions.store', $consultation, absolute: false),
            $html,
        );
        $this->assertStringContainsString('name="return_to" value="specialty"', $html);
        $this->assertStringContainsString('name="specialty_module" value="dentistry"', $html);
    }

    public function test_nurse_without_prescriptions_manage_does_not_see_prescribe_cta(): void
    {
        [$nurseUser, $nurseMember, $visit] = $this->emergencyVisitForRole('ed_nurse', 'er-no-rx');
        $appointment = $visit->appointment()->firstOrFail();
        $appointment->update([
            'status' => Appointment::STATUS_IN_CONSULTATION,
            'started_at' => now(),
            'queue_ticket_status' => 'serving',
        ]);
        Consultation::create([
            'owner_ref' => $this->owner->public_id,
            'visit_id' => $visit->id,
            'appointment_id' => $appointment->id,
            'patient_id' => $visit->patient_id,
            'status' => Consultation::STATUS_DRAFT,
            'started_at' => now(),
        ]);

        $permissions = app(\App\Services\Care\CarePermissions::class);
        $this->assertTrue($this->modules->memberCanManage($this->organization, $nurseMember, 'emergency'));
        $this->assertFalse($permissions->can($nurseMember, 'prescriptions.manage'));

        $this->actingAs($nurseUser)
            ->get(route('care.specialty.workspace', [
                'module' => 'emergency',
                'visit' => $visit,
                'tab' => 'overview',
            ]))
            ->assertOk()
            ->assertDontSee('Prescribe')
            ->assertDontSee('New prescription');
    }

    public function test_specialty_prescribe_store_returns_to_workspace(): void
    {
        [$doctorUser, , $visit] = $this->dentistryVisitForRole('dentist', 'den-rx-store');
        $appointment = $visit->appointment()->firstOrFail();
        $appointment->update([
            'status' => Appointment::STATUS_IN_CONSULTATION,
            'started_at' => now(),
            'queue_ticket_status' => 'serving',
        ]);
        $consultation = Consultation::create([
            'owner_ref' => $this->owner->public_id,
            'visit_id' => $visit->id,
            'appointment_id' => $appointment->id,
            'patient_id' => $visit->patient_id,
            'status' => Consultation::STATUS_DRAFT,
            'started_at' => now(),
        ]);

        $this->actingAs($doctorUser)
            ->post(route('care.prescriptions.store', $consultation), [
                'activate' => true,
                'return_to' => 'specialty',
                'specialty_module' => 'dentistry',
                'specialty_visit_id' => $visit->id,
                'from_consultation' => $consultation->uuid,
                'items' => [
                    [
                        'name' => 'Amoxicillin 500mg',
                        'dosage' => '1 capsule',
                        'frequency' => 'TDS',
                        'duration' => '5 days',
                        'route' => 'oral',
                    ],
                ],
            ])
            ->assertRedirect(route('care.specialty.workspace', [
                'module' => 'dentistry',
                'visit' => $visit,
            ]));

        $this->assertDatabaseHas('care_prescriptions', [
            'consultation_id' => $consultation->id,
            'visit_id' => $visit->id,
            'patient_id' => $visit->patient_id,
        ]);
    }
}
