<?php

namespace Tests\Feature;

use App\Models\Appointment;
use App\Models\Assessment;
use App\Models\Bill;
use App\Models\Branch;
use App\Models\Drug;
use App\Models\InvestigationRequest;
use App\Models\InvestigationType;
use App\Models\Organization;
use App\Models\Patient;
use App\Models\Prescription;
use App\Models\SpecialtyClinicalRecord;
use App\Models\User;
use App\Services\Care\DemoTenantSeeder;
use App\Services\Care\PlanService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Artisan;
use Tests\TestCase;

class DemoSeedCommandTest extends TestCase
{
    use RefreshDatabase;

    public function test_command_is_registered(): void
    {
        $this->assertTrue(collect(Artisan::all())->has('demo:seed'));
    }

    public function test_free_plan_respects_branch_cap_and_skips_paid_modules(): void
    {
        $user = User::create([
            'public_id' => 'demo-free-public-id',
            'name' => 'Ladill Demo (Free)',
            'email' => 'demo-free@ladill.com',
        ]);

        $this->artisan('demo:seed', [
            'identity' => 'demo-free@ladill.com',
            '--plan' => 'free',
        ])->assertSuccessful();

        $org = Organization::query()->where('owner_ref', $user->public_id)->first();
        $this->assertNotNull($org);
        $this->assertSame('free', data_get($org->settings, 'plan'));
        $this->assertTrue((bool) data_get($org->settings, 'onboarded'));
        $this->assertSame(1, (int) data_get($org->settings, 'billed_branches'));
        $this->assertNotEmpty(data_get($org->settings, 'plan_expires_at'));
        $this->assertTrue(\Carbon\Carbon::parse(data_get($org->settings, 'plan_expires_at'))->isFuture());

        $this->assertSame(1, Branch::query()->where('owner_ref', $user->public_id)->where('is_active', true)->count());
        $this->assertSame(1, app(PlanService::class)->maxBranches($org));
        $this->assertGreaterThanOrEqual(12, Patient::query()->where('owner_ref', $user->public_id)->count());
        $this->assertGreaterThanOrEqual(20, Appointment::query()->where('owner_ref', $user->public_id)->count());

        $this->assertSame(0, InvestigationType::query()->where('owner_ref', $user->public_id)->count());
        $this->assertSame(0, InvestigationRequest::query()->where('owner_ref', $user->public_id)->count());
        $this->assertSame(0, Drug::query()->where('owner_ref', $user->public_id)->count());
        $this->assertSame(0, Prescription::query()->where('owner_ref', $user->public_id)->count());
        $this->assertSame(0, Bill::query()->where('owner_ref', $user->public_id)->count());
    }

    public function test_pro_plan_seeds_multi_branch_and_paid_modules(): void
    {
        $user = User::create([
            'public_id' => 'demo-pro-public-id',
            'name' => 'Ladill Demo (Pro)',
            'email' => 'demo-pro@ladill.com',
        ]);

        $this->artisan('demo:seed', [
            'identity' => 'demo-pro@ladill.com',
            '--plan' => 'pro',
        ])->assertSuccessful();

        $org = Organization::query()->where('owner_ref', $user->public_id)->firstOrFail();
        $this->assertSame('pro', app(PlanService::class)->planKey($org));
        $this->assertSame(3, Branch::query()->where('owner_ref', $user->public_id)->count());
        $this->assertTrue(app(PlanService::class)->canUseProModules($org));

        $this->assertGreaterThanOrEqual(12, InvestigationType::query()->where('organization_id', $org->id)->count());
        $this->assertGreaterThan(0, InvestigationRequest::query()->where('owner_ref', $user->public_id)->count());
        $this->assertGreaterThan(0, Drug::query()->where('owner_ref', $user->public_id)->count());
        $this->assertGreaterThan(0, Prescription::query()->where('owner_ref', $user->public_id)->count());
        $this->assertGreaterThan(0, Bill::query()->where('owner_ref', $user->public_id)->count());

        $this->actingAs($user)
            ->withoutMiddleware(\App\Http\Middleware\EnsurePlatformSession::class)
            ->get(route('care.lab.catalog.index'))
            ->assertOk()
            ->assertSee('Full Blood Count')
            ->assertSee('Chest X-Ray')
            ->assertDontSee('No tests in catalog.');

        $catalog = array_keys(config('care.specialty_modules', []));
        $this->assertNotEmpty($catalog);
        foreach ($catalog as $key) {
            $this->assertTrue(
                (bool) data_get($org->settings, "specialty_modules.{$key}"),
                "Expected specialty module [{$key}] enabled for pro demo"
            );
            $type = config("care.specialty_modules.{$key}.department_type");
            $this->assertGreaterThan(
                0,
                \App\Models\Department::query()
                    ->where('owner_ref', $user->public_id)
                    ->where('type', $type)
                    ->where('is_active', true)
                    ->count(),
                "Expected active departments for [{$key}]"
            );
            $this->assertGreaterThan(
                0,
                Appointment::query()
                    ->where('owner_ref', $user->public_id)
                    ->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
                    ->whereHas('department', fn ($q) => $q->where('type', $type))
                    ->count(),
                "Expected waiting specialty appointments for [{$key}]"
            );
        }

        $inventoryNotes = SpecialtyClinicalRecord::query()
            ->where('owner_ref', $user->public_id)
            ->where('module_key', 'blood_bank')
            ->where('record_type', 'inventory_note')
            ->get();
        $this->assertGreaterThan(
            0,
            $inventoryNotes->count(),
            'Expected blood bank inventory_note records after pro demo seed'
        );
        $this->assertNotNull($inventoryNotes->first()?->payload['packed_rbc_units'] ?? null);
        $this->assertTrue(
            $inventoryNotes->contains(fn (SpecialtyClinicalRecord $r) => ! empty($r->payload['low_stock_alert'])),
            'Expected at least one low-stock inventory demo flag'
        );
    }

    public function test_free_demo_does_not_enable_specialty_modules(): void
    {
        $user = User::create([
            'public_id' => 'demo-free-specialty-id',
            'name' => 'Ladill Demo (Free)',
            'email' => 'demo-free@ladill.com',
        ]);

        $this->artisan('demo:seed', [
            'identity' => 'demo-free@ladill.com',
            '--plan' => 'free',
        ])->assertSuccessful();

        $org = Organization::query()->where('owner_ref', $user->public_id)->firstOrFail();
        foreach (array_keys(config('care.specialty_modules', [])) as $key) {
            $this->assertFalse((bool) data_get($org->settings, "specialty_modules.{$key}"));
        }
    }

    public function test_enterprise_seeds_assessments_and_more_branches(): void
    {
        $user = User::create([
            'public_id' => 'demo-enterprise-public-id',
            'name' => 'Accra Healthcare Group',
            'email' => 'demo-enterprise@ladill.com',
        ]);

        $this->artisan('demo:seed', [
            'identity' => $user->public_id,
            '--plan' => 'enterprise',
        ])->assertSuccessful();

        $org = Organization::query()->where('owner_ref', $user->public_id)->firstOrFail();
        $this->assertSame('enterprise', app(PlanService::class)->planKey($org));
        $this->assertSame(6, Branch::query()->where('owner_ref', $user->public_id)->count());
        $this->assertGreaterThan(0, Assessment::query()->where('owner_ref', $user->public_id)->count());
        $this->assertGreaterThan(0, Bill::query()->where('owner_ref', $user->public_id)->count());
    }

    public function test_idempotent_reseed_without_reset_does_not_duplicate(): void
    {
        $user = User::create([
            'public_id' => 'demo-idempotent-id',
            'name' => 'Idempotent Demo',
            'email' => 'demo-idempotent@ladill.com',
        ]);

        $this->artisan('demo:seed', [
            'identity' => 'demo-idempotent@ladill.com',
            '--plan' => 'free',
        ])->assertSuccessful();

        $patients = Patient::query()->where('owner_ref', $user->public_id)->count();
        $appointments = Appointment::query()->where('owner_ref', $user->public_id)->count();
        $orgs = Organization::query()->where('owner_ref', $user->public_id)->count();

        $this->artisan('demo:seed', [
            'identity' => 'demo-idempotent@ladill.com',
            '--plan' => 'free',
        ])->assertSuccessful();

        $this->assertSame($patients, Patient::query()->where('owner_ref', $user->public_id)->count());
        $this->assertSame($appointments, Appointment::query()->where('owner_ref', $user->public_id)->count());
        $this->assertSame($orgs, Organization::query()->where('owner_ref', $user->public_id)->count());
        $this->assertSame(1, Branch::query()->where('owner_ref', $user->public_id)->count());

        $organization = Organization::query()->where('owner_ref', $user->public_id)->first();
        $this->assertTrue((bool) data_get($organization?->settings, 'demo'));
        $this->assertFalse((bool) data_get($organization?->settings, 'rollout.workflow_engine'));
        $this->assertFalse((bool) data_get($organization?->settings, 'rollout.financial_gates'));
    }

    public function test_reset_wipes_only_tenant_scoped_data_then_reseeds(): void
    {
        $keep = User::create([
            'public_id' => 'other-owner',
            'name' => 'Other',
            'email' => 'other@ladill.com',
        ]);
        Organization::create([
            'owner_ref' => $keep->public_id,
            'name' => 'Keep Me',
            'slug' => 'keep-me',
            'settings' => ['onboarded' => true, 'plan' => 'free'],
        ]);
        Branch::create([
            'owner_ref' => $keep->public_id,
            'organization_id' => Organization::query()->where('owner_ref', $keep->public_id)->value('id'),
            'name' => 'Keep Branch',
            'is_active' => true,
        ]);

        $user = User::create([
            'public_id' => 'demo-reset-id',
            'name' => 'Reset Demo',
            'email' => 'demo-reset@ladill.com',
        ]);

        $this->artisan('demo:seed', [
            'identity' => 'demo-reset@ladill.com',
            '--plan' => 'pro',
        ])->assertSuccessful();

        $patientCount = Patient::query()->where('owner_ref', $user->public_id)->count();
        $this->assertGreaterThan(0, $patientCount);
        $this->assertGreaterThan(0, Bill::query()->where('owner_ref', $user->public_id)->count());

        $this->artisan('demo:seed', [
            'identity' => 'demo-reset@ladill.com',
            '--plan' => 'free',
            '--reset' => true,
        ])->assertSuccessful();

        $this->assertSame(1, Branch::query()->where('owner_ref', $user->public_id)->count());
        $this->assertSame(0, Bill::query()->where('owner_ref', $user->public_id)->count());
        $this->assertSame(0, Drug::query()->where('owner_ref', $user->public_id)->count());
        $this->assertSame('free', data_get(
            Organization::query()->where('owner_ref', $user->public_id)->value('settings'),
            'plan'
        ));

        // Other tenant untouched.
        $this->assertSame(1, Organization::query()->where('owner_ref', $keep->public_id)->count());
        $this->assertSame(1, Branch::query()->where('owner_ref', $keep->public_id)->count());
    }

    public function test_reset_keeps_organization_onboarded_mid_wipe(): void
    {
        $user = User::create([
            'public_id' => 'demo-mid-reset-id',
            'name' => 'Mid Reset Demo',
            'email' => 'demo-mid-reset@ladill.com',
        ]);

        $this->artisan('demo:seed', [
            'identity' => 'demo-mid-reset@ladill.com',
            '--plan' => 'free',
        ])->assertSuccessful();

        $orgId = (int) Organization::query()->where('owner_ref', $user->public_id)->value('id');
        $this->assertGreaterThan(0, $orgId);

        app(DemoTenantSeeder::class)->resetTenant($user->public_id);

        $org = Organization::query()->find($orgId);
        $this->assertNotNull($org);
        $this->assertTrue((bool) data_get($org->settings, 'onboarded'));
        $this->assertTrue(app(\App\Services\Care\OrganizationResolver::class)->isOnboarded($user));
        $this->assertSame(0, Branch::query()->where('owner_ref', $user->public_id)->count());
    }

    public function test_creates_local_mirror_when_public_id_and_email_provided(): void
    {
        $this->assertDatabaseMissing('users', ['email' => 'new-demo@ladill.com']);

        $this->artisan('demo:seed', [
            '--public-id' => 'brand-new-public-id',
            '--email' => 'new-demo@ladill.com',
            '--name' => 'New Demo',
            '--plan' => 'free',
        ])->assertSuccessful();

        $user = User::query()->where('email', 'new-demo@ladill.com')->first();
        $this->assertNotNull($user);
        $this->assertSame('brand-new-public-id', $user->public_id);
        $this->assertSame(1, Organization::query()->where('owner_ref', $user->public_id)->count());
    }

    public function test_fails_when_mirror_missing_and_insufficient_identity(): void
    {
        $this->artisan('demo:seed', [
            'identity' => 'missing@ladill.com',
            '--plan' => 'free',
        ])->assertFailed();
    }

    public function test_volumes_helper_marks_free_without_paid_modules(): void
    {
        $volumes = app(DemoTenantSeeder::class)->volumes('free');
        $this->assertFalse($volumes['paid_modules']);
        $this->assertSame(1, $volumes['branches']);
        $this->assertSame(0, $volumes['assessments']);

        $pro = app(DemoTenantSeeder::class)->volumes('pro');
        $this->assertTrue($pro['paid_modules']);
        $this->assertSame(3, $pro['branches']);
    }

    public function test_demo_world_continuity_and_staff_roles(): void
    {
        $user = User::create([
            'public_id' => 'demo-world-care-id',
            'name' => 'Ladill Demo (Pro)',
            'email' => 'demo-pro@ladill.com',
        ]);

        $this->artisan('demo:seed', [
            'identity' => 'demo-pro@ladill.com',
            '--plan' => 'pro',
        ])->assertSuccessful();

        $org = Organization::query()->where('owner_ref', $user->public_id)->firstOrFail();
        $this->assertSame(\App\Support\DemoWorld::business('pro')['name'], $org->name);

        $ama = \App\Support\DemoWorld::personByKey('ama-mensah');
        $this->assertNotNull($ama);
        $patient = Patient::query()
            ->where('owner_ref', $user->public_id)
            ->where('phone', $ama['phone'])
            ->first();
        $this->assertNotNull($patient);
        $this->assertSame($ama['email'], $patient->email);
        $this->assertSame($ama['national_id'], $patient->national_id);

        $this->assertTrue(
            \App\Models\Member::query()
                ->where('owner_ref', $user->public_id)
                ->where('user_ref', 'demo-pro-doctor@ladill.com')
                ->where('role', 'doctor')
                ->exists()
        );
        $this->assertTrue(
            \App\Models\Member::query()
                ->where('owner_ref', $user->public_id)
                ->where('user_ref', 'demo-pro-receptionist@ladill.com')
                ->where('role', 'receptionist')
                ->exists()
        );

        $waiters = Appointment::query()
            ->where('owner_ref', $user->public_id)
            ->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
            ->get();
        $this->assertNotEmpty($waiters);
        foreach ($waiters as $waiter) {
            $this->assertNotNull($waiter->practitioner_id, 'Demo waiters must all have a doctor assigned');
            $this->assertNotSame('unresolved', $waiter->queue_routing_status);
        }

        $branches = Branch::query()->where('organization_id', $org->id)->orderBy('id')->get();
        $this->assertGreaterThanOrEqual(3, $branches->count());
        foreach ($branches as $branch) {
            $unpaid = Bill::query()
                ->where('organization_id', $org->id)
                ->where('branch_id', $branch->id)
                ->where('balance_minor', '>', 0)
                ->count();
            $this->assertGreaterThan(
                0,
                $unpaid,
                "Branch {$branch->name} must have unpaid demo invoices for cashiers",
            );
        }

        $cashier = \App\Models\Member::query()
            ->where('organization_id', $org->id)
            ->where('role', 'cashier')
            ->first();
        $this->assertNotNull($cashier);
        $this->assertSame((int) $branches->first()->id, (int) $cashier->branch_id);
    }

    public function test_specialty_doctor_links_after_sso_public_id_remap(): void
    {
        $owner = User::create([
            'public_id' => 'demo-pro-specialty-link-owner',
            'name' => 'Ladill Demo (Pro)',
            'email' => 'demo-pro@ladill.com',
        ]);

        $this->artisan('demo:seed', [
            'identity' => 'demo-pro@ladill.com',
            '--plan' => 'pro',
        ])->assertSuccessful();

        $org = Organization::query()->where('owner_ref', $owner->public_id)->firstOrFail();

        $dentist = User::create([
            'public_id' => 'demo-pro-dentistry-public-id',
            'name' => 'Dr. Ama Dental (Pro)',
            'email' => 'demo-pro-dentistry@ladill.com',
        ]);

        $member = \App\Models\Member::query()
            ->where('organization_id', $org->id)
            ->where('user_ref', 'demo-pro-dentistry@ladill.com')
            ->firstOrFail();

        // Simulate Identity SSO remapping member.user_ref from email → public_id.
        $member->forceFill(['user_ref' => $dentist->public_id])->save();

        $this->artisan('demo:seed', [
            'identity' => 'demo-pro@ladill.com',
            '--plan' => 'pro',
        ])->assertSuccessful();

        $member->refresh();
        $this->assertSame($dentist->public_id, $member->user_ref);

        $linked = \App\Models\Practitioner::query()
            ->where('organization_id', $org->id)
            ->where('member_id', $member->id)
            ->where('specialty', 'Dentistry')
            ->get();
        $this->assertCount(1, $linked, 'Home-branch dentistry desk must link to the remapped member');
        $this->assertSame((int) $member->branch_id, (int) $linked->first()->branch_id);

        $modules = app(\App\Services\Care\SpecialtyModuleService::class);
        // Dedicated specialist roles (dentist) use primary apps, not legacy desk-specialist matching.
        $this->assertFalse($modules->isDeskSpecialist($org, $member));
        $this->assertSame(['dentistry'], app(\App\Services\Care\CarePermissions::class)->primaryAppsFor($member));
        $keys = collect($modules->enabledModulesForMember($org, $member))->pluck('key')->all();
        $this->assertSame(['dentistry'], $keys);
    }
}
