<?php

namespace Tests\Feature;

use App\Models\Branch;
use App\Models\Business;
use App\Models\DailyClosing;
use App\Models\Expense;
use App\Models\FinancialAccount;
use App\Models\FixedAsset;
use App\Models\PayrollPeriod;
use App\Models\RecurringExpense;
use App\Models\Role;
use App\Models\User;
use App\Services\Authorization\AuthorizationAssignmentService;
use App\Services\PayrollService;
use App\Support\Authorization\PermissionCatalog;
use App\Support\Authorization\PermissionName;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Route;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;

class Phase7OperationalFinanceAuthorizationTest extends TestCase
{
    use RefreshDatabase;

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

    public function test_phase_seven_record_policies_require_permission_and_accessible_branch(): void
    {
        $branchA = $this->branch('P7-A');
        $branchB = $this->branch('P7-B');
        $admin = $this->staff('branch-administrator', $branchA, 'phase7_admin');
        $loanOfficer = $this->staff('loan-officer', $branchA, 'phase7_loan_officer');

        $accountA = new FinancialAccount(['branch_id' => $branchA->id]);
        $accountB = new FinancialAccount(['branch_id' => $branchB->id]);
        $expenseA = new Expense(['branch_id' => $branchA->id]);
        $expenseB = new Expense(['branch_id' => $branchB->id]);
        $recurringA = new RecurringExpense(['branch_id' => $branchA->id]);
        $payrollA = new PayrollPeriod(['branch_id' => $branchA->id]);
        $closingA = new DailyClosing(['branch_id' => $branchA->id]);
        $assetA = new FixedAsset(['branch_id' => $branchA->id]);
        $assetB = new FixedAsset(['branch_id' => $branchB->id]);

        $this->assertTrue(Gate::forUser($admin)->allows('view', $accountA));
        $this->assertTrue(Gate::forUser($admin)->allows('manage', $accountA));
        $this->assertTrue(Gate::forUser($admin)->allows('capital', $accountA));
        $this->assertTrue(Gate::forUser($admin)->allows('transfer', $accountA));
        $this->assertFalse(Gate::forUser($admin)->allows('view', $accountB));

        $this->assertTrue(Gate::forUser($admin)->allows('view', $expenseA));
        $this->assertTrue(Gate::forUser($admin)->allows('decide', $expenseA));
        $this->assertFalse(Gate::forUser($admin)->allows('view', $expenseB));
        $this->assertTrue(Gate::forUser($admin)->allows('manage', $recurringA));
        $this->assertTrue(Gate::forUser($admin)->allows('view', $payrollA));
        $this->assertTrue(Gate::forUser($admin)->allows('decide', $payrollA));
        $this->assertTrue(Gate::forUser($admin)->allows('pay', $payrollA));
        $this->assertTrue(Gate::forUser($admin)->allows('confirm', $closingA));
        $this->assertTrue(Gate::forUser($admin)->allows('manage', $assetA));
        $this->assertTrue(Gate::forUser($admin)->allows('depreciate', $assetA));
        $this->assertTrue(Gate::forUser($admin)->allows('maintenance', $assetA));
        $this->assertFalse(Gate::forUser($admin)->allows('view', $assetB));

        $this->assertFalse(Gate::forUser($loanOfficer)->allows('manage', $accountA));
        $this->assertFalse(Gate::forUser($loanOfficer)->allows('decide', $expenseA));
        $this->assertFalse(Gate::forUser($loanOfficer)->allows('pay', $payrollA));
    }

    public function test_phase_seven_routes_keep_functional_permission_and_add_record_policy_middleware(): void
    {
        $expectations = [
            'accounts.show' => ['permission:accounts.view', 'can:view,account'],
            'accounts.update' => ['permission:accounts.manage', 'can:manage,account'],
            'expenses.show' => ['permission:expenses.view', 'can:view,expense'],
            'expenses.decision' => ['permission:expenses.approve', 'can:decide,expense'],
            'recurring-expenses.update' => ['permission:expenses.recurring.manage', 'can:manage,recurringExpense'],
            'payroll.show' => ['permission:payroll.view', 'can:view,period'],
            'payroll.entries.update' => ['permission:payroll.periods.create', 'can:updateEntries,period'],
            'payroll.decision' => ['permission:payroll.approve', 'can:decide,period'],
            'payroll.pay' => ['permission:payroll.pay', 'can:pay,period'],
            'cash-closing.show' => ['permission:cash-closing.view', 'can:view,dailyClosing'],
            'cash-closing.confirm' => ['permission:daily-closing.confirm', 'can:confirm,dailyClosing'],
            'fixed-assets.show' => ['permission:fixed-assets.view', 'can:view,fixedAsset'],
            'fixed-assets.update' => ['permission:fixed-assets.manage', 'can:manage,fixedAsset'],
            'fixed-assets.depreciate' => ['permission:fixed-assets.depreciate', 'can:depreciate,fixedAsset'],
            'fixed-assets.maintenance' => ['permission:fixed-assets.maintenance', 'can:maintenance,fixedAsset'],
        ];

        foreach ($expectations as $routeName => $middleware) {
            $route = Route::getRoutes()->getByName($routeName);
            $this->assertNotNull($route, "Expected route {$routeName} to exist.");
            $actual = $route->gatherMiddleware();
            foreach ($middleware as $expected) {
                $this->assertContains($expected, $actual, "Route {$routeName} must retain {$expected}.");
            }
        }
    }

    public function test_high_risk_operational_finance_services_recheck_authorization_inside_domain_boundary(): void
    {
        $expectations = [
            'FinancialAccountingService.php' => [
                'PermissionName::ACCOUNTS_MANAGE',
                "authorize('capital'",
                "authorize('transfer'",
            ],
            'ExpenseService.php' => ["authorize('decide'"],
            'PayrollService.php' => [
                'PermissionName::PAYROLL_COMPENSATION_MANAGE',
                'PermissionName::PAYROLL_PERIODS_CREATE',
                "authorize('updateEntries'",
                "authorize('decide'",
                "authorize('pay'",
                'cannot pay it',
            ],
            'DailyClosingService.php' => ['maker_checker_enabled', 'prepared the closing cannot confirm it'],
            'FixedAssetService.php' => ['purchase account must belong to the asset branch', 'same branch as the asset'],
        ];

        foreach ($expectations as $file => $needles) {
            $source = File::get(app_path('Services/'.$file));
            foreach ($needles as $needle) {
                $this->assertStringContainsString($needle, $source, "{$file} is missing Phase 7 safeguard {$needle}.");
            }
        }
    }

    public function test_payroll_preparer_cannot_pay_same_payroll_even_as_super_administrator(): void
    {
        $business = Business::firstOrFail();
        $business->update(['maker_checker_enabled' => true]);
        $branch = $this->branch('P7-PAY');
        $admin = User::where('username', 'admin')->firstOrFail();
        $this->actingAs($admin);

        $period = PayrollPeriod::create([
            'period_number' => 'PAY-P7-2026-000001',
            'business_id' => $business->id,
            'branch_id' => $branch->id,
            'name' => 'Phase 7 Maker Checker Payroll',
            'start_date' => '2026-08-01',
            'end_date' => '2026-08-31',
            'pay_date' => '2026-08-31',
            'status' => 'approved',
            'created_by' => $admin->id,
        ]);
        $account = FinancialAccount::create([
            'branch_id' => $branch->id,
            'name' => 'Phase 7 Payroll Cash',
            'account_type' => 'cash',
            'opening_balance' => '10000.00',
            'current_balance' => '10000.00',
            'status' => 'active',
        ]);

        try {
            app(PayrollService::class)->pay($period, $account, $admin);
            $this->fail('Expected maker-checker to block payroll preparer from paying the same payroll.');
        } catch (ValidationException $exception) {
            $this->assertStringContainsString(
                'prepared this payroll period cannot pay it',
                collect($exception->errors())->flatten()->join(' '),
            );
        }

        $this->assertSame('approved', $period->fresh()->status);
        $this->assertSame('10000.00', $account->fresh()->current_balance);
    }

    public function test_phase_seven_reuses_existing_catalog_without_permission_drift(): void
    {
        $this->assertCount(count(PermissionCatalog::definitions()), PermissionName::cases());
        $this->assertSame(
            PermissionName::values(),
            array_values(array_unique(PermissionName::values())),
            'Canonical permission names must remain unique.',
        );
    }

    private function branch(string $tag): Branch
    {
        $business = Business::firstOrFail();

        return Branch::create([
            'business_id' => $business->id,
            'name' => 'Phase 7 '.$tag,
            'code' => 'P7'.strtoupper(substr(md5($tag), 0, 6)),
            'status' => 'active',
        ]);
    }

    private function staff(string $roleName, Branch $branch, string $username): User
    {
        $user = User::factory()->create([
            'name' => str($username)->replace('_', ' ')->title()->toString(),
            'username' => $username,
            'status' => 'active',
            'primary_branch_id' => $branch->id,
        ]);
        $user->branches()->attach($branch->id, ['is_primary' => true]);
        app(AuthorizationAssignmentService::class)->assignUserRole(
            $user,
            Role::where('name', $roleName)->firstOrFail(),
        );

        return $user->fresh();
    }
}
