<?php

namespace Tests\Feature;

use App\Models\Branch;
use App\Models\Business;
use App\Models\CustomerAppointment;
use App\Models\ManagementAlert;
use App\Models\ReminderNotificationLog;
use App\Models\Role;
use App\Models\Task;
use App\Models\User;
use App\Services\Authorization\AuthorizationAssignmentService;
use App\Support\Authorization\DefaultRoleMatrix;
use App\Support\Authorization\EffectiveRoleMatrix;
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 Tests\TestCase;

class Phase8CrossSystemAuthorizationTest extends TestCase
{
    use RefreshDatabase;

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

    public function test_phase_eight_catalog_contains_112_unique_permissions_and_deprecates_old_management_umbrellas(): void
    {
        $this->assertCount(112, PermissionName::cases());
        $this->assertSame(PermissionName::values(), array_values(array_unique(PermissionName::values())));

        $legacy = PermissionCatalog::legacyPermissions();
        foreach ([PermissionName::TASKS_MANAGE, PermissionName::APPOINTMENTS_MANAGE, PermissionName::MANAGEMENT_ALERTS_MANAGE] as $permission) {
            $this->assertArrayHasKey($permission->value, $legacy);
        }

        $this->assertSame(
            ['tasks.create', 'tasks.update', 'tasks.assign', 'tasks.update-status'],
            $legacy[PermissionName::TASKS_MANAGE->value]['replacement'],
        );
        $this->assertSame(
            ['appointments.create', 'appointments.update', 'appointments.manage-status'],
            $legacy[PermissionName::APPOINTMENTS_MANAGE->value]['replacement'],
        );
        $this->assertSame(
            ['management-alerts.acknowledge', 'management-alerts.resolve', 'management-alerts.settings'],
            $legacy[PermissionName::MANAGEMENT_ALERTS_MANAGE->value]['replacement'],
        );
    }

    public function test_effective_role_matrix_preserves_previous_access_while_granting_phase_eight_replacements(): void
    {
        $baseline = DefaultRoleMatrix::roles();
        $effective = EffectiveRoleMatrix::roles();

        foreach ($effective as $roleName => $definition) {
            if ($roleName === 'super-administrator') {
                $this->assertSame([], $definition['permissions']);
                continue;
            }

            $permissions = collect($definition['permissions']);
            $this->assertTrue($permissions->contains(PermissionName::DASHBOARD_VIEW->value), "{$roleName} lost authenticated dashboard access.");
            $this->assertTrue($permissions->contains(PermissionName::SEARCH_USE->value), "{$roleName} lost authenticated search access.");

            $old = collect($baseline[$roleName]['permissions']);
            if ($old->contains(PermissionName::REPORTS_VIEW->value)) {
                $this->assertTrue($permissions->contains(PermissionName::REPORTS_EXPORT->value));
            }
            if ($old->contains(PermissionName::FIXED_ASSETS_VIEW->value)) {
                $this->assertTrue($permissions->contains(PermissionName::FIXED_ASSETS_REPORTS_VIEW->value));
                $this->assertTrue($permissions->contains(PermissionName::FIXED_ASSETS_REPORTS_EXPORT->value));
            }
            if ($old->contains(PermissionName::TASKS_MANAGE->value)) {
                foreach ([PermissionName::TASKS_CREATE, PermissionName::TASKS_UPDATE, PermissionName::TASKS_ASSIGN, PermissionName::TASKS_UPDATE_STATUS] as $permission) {
                    $this->assertTrue($permissions->contains($permission->value));
                }
            }
            if ($old->contains(PermissionName::APPOINTMENTS_MANAGE->value)) {
                foreach ([PermissionName::APPOINTMENTS_CREATE, PermissionName::APPOINTMENTS_UPDATE, PermissionName::APPOINTMENTS_MANAGE_STATUS] as $permission) {
                    $this->assertTrue($permissions->contains($permission->value));
                }
            }
            if ($old->contains(PermissionName::MANAGEMENT_ALERTS_MANAGE->value)) {
                foreach ([PermissionName::MANAGEMENT_ALERTS_ACKNOWLEDGE, PermissionName::MANAGEMENT_ALERTS_RESOLVE, PermissionName::MANAGEMENT_ALERTS_SETTINGS] as $permission) {
                    $this->assertTrue($permissions->contains($permission->value));
                }
            }
        }
    }

    public function test_phase_eight_record_policies_combine_granular_permissions_with_branch_scope(): void
    {
        $branchA = $this->branch('P8-A');
        $branchB = $this->branch('P8-B');
        $manager = $this->staff('branch-manager', $branchA, 'phase8_manager');
        $officer = $this->staff('loan-officer', $branchA, 'phase8_officer');
        $admin = $this->staff('branch-administrator', $branchA, 'phase8_admin');

        $ownTask = new Task(['branch_id' => $branchA->id, 'assigned_to' => $officer->id]);
        $otherTask = new Task(['branch_id' => $branchA->id, 'assigned_to' => $manager->id]);
        $foreignTask = new Task(['branch_id' => $branchB->id, 'assigned_to' => $officer->id]);

        $this->assertTrue(Gate::forUser($manager)->allows('update', $otherTask));
        $this->assertTrue(Gate::forUser($manager)->allows('updateStatus', $otherTask));
        $this->assertTrue(Gate::forUser($officer)->allows('updateStatus', $ownTask));
        $this->assertFalse(Gate::forUser($officer)->allows('updateStatus', $otherTask));
        $this->assertFalse(Gate::forUser($officer)->allows('updateStatus', $foreignTask));

        $appointment = new CustomerAppointment(['branch_id' => $branchA->id]);
        $foreignAppointment = new CustomerAppointment(['branch_id' => $branchB->id]);
        $this->assertTrue(Gate::forUser($officer)->allows('manageStatus', $appointment));
        $this->assertFalse(Gate::forUser($officer)->allows('manageStatus', $foreignAppointment));

        $alert = new ManagementAlert(['branch_id' => $branchA->id]);
        $foreignAlert = new ManagementAlert(['branch_id' => $branchB->id]);
        $this->assertTrue(Gate::forUser($manager)->allows('acknowledge', $alert));
        $this->assertTrue(Gate::forUser($manager)->allows('resolve', $alert));
        $this->assertFalse(Gate::forUser($manager)->allows('resolve', $foreignAlert));

        $notification = new ReminderNotificationLog(['branch_id' => $branchA->id]);
        $foreignNotification = new ReminderNotificationLog(['branch_id' => $branchB->id]);
        $this->assertTrue(Gate::forUser($admin)->allows('retry', $notification));
        $this->assertFalse(Gate::forUser($admin)->allows('retry', $foreignNotification));
    }

    public function test_phase_eight_routes_use_granular_permissions_and_record_policies(): void
    {
        $expectations = [
            'dashboard' => ['permission:dashboard.view'],
            'global-search.index' => ['permission:search.use'],
            'reports.export' => ['permission:reports.export'],
            'loan-branch-reports.export' => ['permission:reports.export'],
            'fixed-assets.reports' => ['permission:fixed-assets.reports.view'],
            'fixed-assets.reports.export' => ['permission:fixed-assets.reports.export'],
            'tasks.create' => ['permission:tasks.create'],
            'tasks.update' => ['permission:tasks.update', 'can:update,task'],
            'tasks.status' => ['permission:tasks.update-status', 'can:updateStatus,task'],
            'calendar.appointments.store' => ['permission:appointments.create'],
            'calendar.appointments.status' => ['permission:appointments.manage-status', 'can:manageStatus,appointment'],
            'management-alerts.acknowledge' => ['permission:management-alerts.acknowledge', 'can:acknowledge,alert'],
            'management-alerts.resolve' => ['permission:management-alerts.resolve', 'can:resolve,alert'],
            'management-alerts.settings' => ['permission:management-alerts.settings'],
            'notifications.retry' => ['permission:notifications.retry', 'can:retry,notification'],
            'loans.documents.review' => ['permission:loans.view', 'can:view,loan'],
            'documents.payment-receipt.pdf' => ['permission:loans.view', 'can:view,payment'],
            'documents.collateral-release.pdf' => ['permission:collateral.view', 'can:view,collateral'],
            'documents.disbursement-receipt.pdf' => ['permission:loans.view', 'can:view,disbursement'],
            'documents.expense-voucher.pdf' => ['permission:expenses.view', 'can:view,expense'],
        ];

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

    public function test_cross_cutting_services_and_controllers_recheck_phase_eight_authorization(): void
    {
        $expectations = [
            app_path('Services/GlobalSearchService.php') => ["authorize(PermissionName::SEARCH_USE", 'PermissionName::CUSTOMERS_VIEW', 'PermissionName::LOANS_VIEW'],
            app_path('Http/Controllers/DashboardController.php') => ['permission:dashboard.view', "authorize(PermissionName::DASHBOARD_VIEW"],
            app_path('Http/Controllers/TaskController.php') => ["authorize('updateStatus'", 'PermissionName::TASKS_ASSIGN'],
            app_path('Http/Controllers/ManagementCalendarController.php') => ["authorize('manageStatus'", 'PermissionName::APPOINTMENTS_CREATE'],
            app_path('Http/Controllers/ManagementAlertController.php') => ["authorize('acknowledge'", "authorize('resolve'", 'PermissionName::MANAGEMENT_ALERTS_SETTINGS'],
            app_path('Http/Controllers/ReminderNotificationController.php') => ["authorize('retry'"],
        ];

        foreach ($expectations as $path => $needles) {
            $source = File::get($path);
            foreach ($needles as $needle) {
                $this->assertStringContainsString($needle, $source, basename($path)." is missing Phase 8 safeguard {$needle}.");
            }
        }
    }

    public function test_phase_eight_navigation_and_action_surfaces_reference_granular_permissions(): void
    {
        $expectations = [
            resource_path('views/layouts/partials/topbar.blade.php') => ["@can('search.use')"],
            resource_path('views/layouts/partials/sidebar.blade.php') => ["can('dashboard.view')"],
            resource_path('views/tasks/index.blade.php') => ["@can('tasks.create')"],
            resource_path('views/tasks/show.blade.php') => ["@can('updateStatus',\$task)"],
            resource_path('views/calendar/index.blade.php') => ["@can('appointments.create')"],
            resource_path('views/management-alerts/index.blade.php') => ["@can('acknowledge',\$alert)", "@can('resolve',\$alert)", "@can('management-alerts.settings')"],
            resource_path('views/notifications/index.blade.php') => ["@can('retry',\$log)"],
            resource_path('views/fixed-assets/reports.blade.php') => ["@can('fixed-assets.reports.export')"],
        ];

        foreach ($expectations as $path => $needles) {
            $source = File::get($path);
            foreach ($needles as $needle) {
                $this->assertStringContainsString($needle, $source, basename($path)." is missing Phase 8 UI authorization {$needle}.");
            }
        }
    }

    public function test_dashboard_and_search_are_no_longer_authenticated_exemptions(): void
    {
        $exemptions = config('authorization_inventory.authenticated_exemptions');
        $this->assertArrayNotHasKey('dashboard', $exemptions);
        $this->assertArrayNotHasKey('global-search.index', $exemptions);
        $this->assertArrayHasKey('document-branding.asset', $exemptions);
    }

    private function branch(string $tag): Branch
    {
        $business = Business::firstOrFail();
        return Branch::create([
            'business_id' => $business->id,
            'name' => 'Phase 8 '.$tag,
            'code' => 'P8'.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();
    }
}
