<?php

namespace Tests\Feature\BranchIsolation;

use App\Models\Branch;
use App\Models\Business;
use App\Models\Customer;
use App\Models\CustomerDocument;
use App\Models\FinancialAccount;
use App\Models\FinancialTransfer;
use App\Services\BranchContext;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use RuntimeException;
use Tests\TestCase;

class BranchContextTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed(DatabaseSeeder::class);
    }

    public function test_non_http_queries_fail_closed_and_explicit_context_is_restored(): void
    {
        [$branchA, $branchB, $customerA, $customerB] = $this->customerFixture();
        $context = app(BranchContext::class);

        $this->assertSame([], Customer::query()->pluck('id')->all());

        $branchAIds = $context->forBranch($branchA->id, fn () => Customer::query()->pluck('id')->all());
        $this->assertSame([$customerA->id], $branchAIds);
        $this->assertSame([], Customer::query()->pluck('id')->all());

        $systemIds = $context->systemWide(fn () => Customer::query()->orderBy('id')->pluck('id')->all());
        $this->assertSame([$customerA->id, $customerB->id], $systemIds);
        $this->assertSame([], Customer::query()->pluck('id')->all());

        try {
            $context->systemWide(function (): void {
                throw new RuntimeException('stop');
            });
            $this->fail('Expected context callback exception.');
        } catch (RuntimeException $exception) {
            $this->assertSame('stop', $exception->getMessage());
        }

        $this->assertFalse($context->isSystemWide());
        $this->assertNull($context->branchIds());
        $this->assertSame([], Customer::query()->pluck('id')->all());
    }

    public function test_parent_owned_children_follow_explicit_branch_context(): void
    {
        [$branchA, $branchB, $customerA, $customerB] = $this->customerFixture();
        $context = app(BranchContext::class);

        $documentA = CustomerDocument::create([
            'customer_id' => $customerA->id,
            'document_type' => 'ID',
            'original_name' => 'a.pdf',
            'file_path' => 'customers/a.pdf',
        ]);
        $documentB = CustomerDocument::create([
            'customer_id' => $customerB->id,
            'document_type' => 'ID',
            'original_name' => 'b.pdf',
            'file_path' => 'customers/b.pdf',
        ]);

        $this->assertSame([], CustomerDocument::query()->pluck('id')->all());
        $this->assertSame(
            [$documentA->id],
            $context->forBranch($branchA->id, fn () => CustomerDocument::query()->pluck('id')->all())
        );
        $this->assertSame(
            [$documentB->id],
            $context->forBranch($branchB->id, fn () => CustomerDocument::query()->pluck('id')->all())
        );
    }

    public function test_indirect_financial_transfer_scope_requires_both_accounts_in_context(): void
    {
        $business = Business::firstOrFail();
        $branchA = Branch::create(['business_id' => $business->id, 'name' => 'Context A', 'status' => 'active']);
        $branchB = Branch::create(['business_id' => $business->id, 'name' => 'Context B', 'status' => 'active']);
        $context = app(BranchContext::class);

        $a1 = $this->account($branchA, 'A Cash 1');
        $a2 = $this->account($branchA, 'A Cash 2');
        $b1 = $this->account($branchB, 'B Cash 1');
        $b2 = $this->account($branchB, 'B Cash 2');

        $aa = $this->transfer('CTX-AA', $a1, $a2);
        $ab = $this->transfer('CTX-AB', $a1, $b1);
        $bb = $this->transfer('CTX-BB', $b1, $b2);

        $this->assertSame([], FinancialTransfer::query()->pluck('id')->all());
        $this->assertSame(
            [$aa->id],
            $context->forBranch($branchA->id, fn () => FinancialTransfer::query()->pluck('id')->all())
        );
        $this->assertSame(
            [$aa->id, $ab->id, $bb->id],
            $context->forBranches([$branchA->id, $branchB->id], fn () => FinancialTransfer::query()->orderBy('id')->pluck('id')->all())
        );
        $this->assertSame(
            [$aa->id, $ab->id, $bb->id],
            $context->systemWide(fn () => FinancialTransfer::query()->orderBy('id')->pluck('id')->all())
        );
    }

    private function customerFixture(): array
    {
        $business = Business::firstOrFail();
        $branchA = Branch::create(['business_id' => $business->id, 'name' => 'Context A', 'status' => 'active']);
        $branchB = Branch::create(['business_id' => $business->id, 'name' => 'Context B', 'status' => 'active']);

        $customerA = Customer::create([
            'branch_id' => $branchA->id,
            'customer_number' => 'CTX-A-001',
            'identity_mode' => 'without_nin',
            'first_name' => 'Alpha',
            'last_name' => 'Customer',
            'phone' => '0711000001',
            'status' => 'active',
        ]);
        $customerB = Customer::create([
            'branch_id' => $branchB->id,
            'customer_number' => 'CTX-B-001',
            'identity_mode' => 'without_nin',
            'first_name' => 'Beta',
            'last_name' => 'Customer',
            'phone' => '0711000002',
            'status' => 'active',
        ]);

        return [$branchA, $branchB, $customerA, $customerB];
    }

    private function account(Branch $branch, string $name): FinancialAccount
    {
        return FinancialAccount::create([
            'branch_id' => $branch->id,
            'name' => $name,
            'account_type' => 'cash',
            'opening_balance' => '1000.00',
            'current_balance' => '1000.00',
            'status' => 'active',
        ]);
    }

    private function transfer(string $number, FinancialAccount $source, FinancialAccount $destination): FinancialTransfer
    {
        return FinancialTransfer::create([
            'transfer_number' => $number,
            'source_account_id' => $source->id,
            'destination_account_id' => $destination->id,
            'amount' => '100.00',
            'transfer_date' => today()->toDateString(),
        ]);
    }
}
