<?php

namespace Tests\Feature;

use App\Models\Role;
use App\Models\User;
use App\Services\Authorization\AuthorizationAssignmentService;
use App\Support\Authorization\DefaultRoleMatrix;
use App\Support\Authorization\PermissionCatalog;
use App\Support\Authorization\PermissionName;
use Database\Seeders\AuthorizationSeeder;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Route;
use Tests\TestCase;

class AuthorizationCatalogTest extends TestCase
{
    use RefreshDatabase;

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

    public function test_permission_enum_catalog_and_seeded_database_are_identical(): void
    {
        $catalogNames = PermissionCatalog::names();
        $enumNames = PermissionName::values();
        sort($catalogNames);
        sort($enumNames);

        $this->assertCount(112, $catalogNames);
        $this->assertSame($enumNames, $catalogNames);
        $this->assertSame(112, \App\Models\Permission::query()->count());
        $this->assertSame(0, Artisan::call('authorization:catalog-check', ['--json' => true]), Artisan::output());
    }

    public function test_default_role_matrix_preserves_phase_two_access_except_reviewed_phase_four_and_phase_six_grants(): void
    {
        $expectedCounts = [
            'super-administrator' => 0,
            'branch-administrator' => 85,
            'branch-manager' => 72,
            'loan-officer' => 31,
            'loan-inspector' => 20,
            'cashier' => 19,
            'accountant' => 40,
            'debt-collector' => 18,
            'hr-payroll-officer' => 8,
            'auditor' => 19,
        ];

        $matrix = [];
        $actualCounts = [];
        foreach (DefaultRoleMatrix::roles() as $roleName => $definition) {
            $permissions = $definition['permissions'];
            sort($permissions);
            $matrix[$roleName] = $permissions;
            $actualCounts[$roleName] = count($permissions);
        }
        ksort($matrix);

        $this->assertSame($expectedCounts, $actualCounts);
        $this->assertContains(PermissionName::ROLES_ASSIGN->value, $matrix['branch-administrator']);
        $this->assertContains(PermissionName::ROLES_ASSIGN_SYSTEM->value, $matrix['branch-administrator']);
        $this->assertNotContains(PermissionName::ROLES_ASSIGN_SUPER_ADMIN->value, $matrix['branch-administrator']);

        foreach (['branch-administrator', 'branch-manager', 'loan-officer'] as $roleName) {
            $this->assertContains(PermissionName::LOANS_INSTALLMENTS_EXCUSE->value, $matrix[$roleName]);
        }
        foreach (['loan-inspector', 'cashier', 'accountant', 'debt-collector', 'hr-payroll-officer', 'auditor'] as $roleName) {
            $this->assertNotContains(PermissionName::LOANS_INSTALLMENTS_EXCUSE->value, $matrix[$roleName]);
        }

        $phaseTwoBaseline = $matrix;
        $phaseTwoBaseline['branch-administrator'] = array_values(array_diff(
            $phaseTwoBaseline['branch-administrator'],
            [
                PermissionName::ROLES_ASSIGN->value,
                PermissionName::ROLES_ASSIGN_SYSTEM->value,
                PermissionName::LOANS_INSTALLMENTS_EXCUSE->value,
            ],
        ));
        $phaseTwoBaseline['branch-manager'] = array_values(array_diff(
            $phaseTwoBaseline['branch-manager'],
            [PermissionName::LOANS_INSTALLMENTS_EXCUSE->value],
        ));
        $phaseTwoBaseline['loan-officer'] = array_values(array_diff(
            $phaseTwoBaseline['loan-officer'],
            [PermissionName::LOANS_INSTALLMENTS_EXCUSE->value],
        ));
        sort($phaseTwoBaseline['branch-administrator']);
        sort($phaseTwoBaseline['branch-manager']);
        sort($phaseTwoBaseline['loan-officer']);

        $this->assertSame(
            'c39ca36d08e8adff58547662d551e89abd2656dea3608601869616646cff931a',
            hash('sha256', json_encode($phaseTwoBaseline, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR)),
            'The historical DefaultRoleMatrix remains the reviewed parity baseline; Phase 8 effective grants are layered through EffectiveRoleMatrix.',
        );
    }

    public function test_every_permission_protected_route_uses_a_catalog_permission(): void
    {
        $catalog = PermissionCatalog::names();

        foreach (Route::getRoutes()->getRoutes() as $route) {
            foreach ($route->gatherMiddleware() as $middleware) {
                if (! is_string($middleware) || ! str_starts_with($middleware, 'permission:')) {
                    continue;
                }

                $permissions = preg_split('/[|,]/', substr($middleware, strlen('permission:'))) ?: [];
                foreach ($permissions as $permission) {
                    $permission = trim($permission);
                    if ($permission === '') {
                        continue;
                    }

                    $this->assertContains(
                        $permission,
                        $catalog,
                        "Route {$route->getName()} references {$permission}, which is absent from PermissionCatalog.",
                    );
                }
            }
        }
    }

    public function test_legacy_permission_registry_is_derived_consistently_from_the_catalog(): void
    {
        $legacy = PermissionCatalog::legacyPermissions();
        $this->assertSame([
            'appointments.manage',
            'collections.manage',
            'customers.manage',
            'expenses.manage',
            'management-alerts.manage',
            'payroll.manage',
            'tasks.manage',
        ], collect(array_keys($legacy))->sort()->values()->all());

        $this->assertSame($legacy, config('authorization_inventory.legacy_broad_permissions'));
    }

    public function test_authorization_seeding_is_idempotent_and_preserves_custom_roles_and_user_assignments(): void
    {
        $user = User::factory()->create();
        $customRole = Role::query()->create([
            'name' => 'custom-operations-reviewer',
            'guard_name' => PermissionCatalog::GUARD,
            'display_name' => 'Custom Operations Reviewer',
            'is_system' => false,
        ]);
        app(AuthorizationAssignmentService::class)->assignUserRole($user, $customRole);

        $this->seed(AuthorizationSeeder::class);
        $this->seed(AuthorizationSeeder::class);

        $this->assertDatabaseHas('roles', [
            'id' => $customRole->id,
            'name' => 'custom-operations-reviewer',
            'is_system' => false,
        ]);
        $this->assertTrue($user->fresh()->hasRole('custom-operations-reviewer'));
        $this->assertSame(112, \App\Models\Permission::query()->count());
        $this->assertSame(10, Role::query()->where('is_system', true)->count());
    }

    public function test_database_seeder_has_only_one_authoritative_permission_seeder(): void
    {
        $source = File::get(database_path('seeders/DatabaseSeeder.php'));

        $this->assertStringContainsString('AuthorizationSeeder::class', $source);
        $this->assertStringNotContainsString('RolePermissionSeeder::class', $source);
        $this->assertStringNotContainsString('PayrollPermissionSeeder::class', $source);
        $this->assertStringNotContainsString('CashClosingPermissionSeeder::class', $source);
        $this->assertStringNotContainsString('ManagementWorkPermissionSeeder::class', $source);
    }
}
