<?php

namespace Tests\Feature;

use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;
use Tests\TestCase;

class AuthorizationInventoryTest extends TestCase
{
    use RefreshDatabase;

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

    public function test_strict_authorization_inventory_classifies_all_application_routes(): void
    {
        $report = $this->inventoryReport('authorization-strict.json', strict: true);

        $this->assertSame(1, $report['schema_version']);
        $this->assertSame('pass', $report['status']);
        $this->assertSame([], $report['findings']['errors']);
        $this->assertGreaterThan(0, $report['summary']['routes']);
        $this->assertGreaterThan(0, $report['summary']['permissions']);
        $this->assertGreaterThan(0, $report['summary']['roles']);

        $classifications = collect($report['routes'])
            ->where('application_managed', true)
            ->pluck('classification')
            ->unique()
            ->all();

        $this->assertNotContains('unclassified-authenticated', $classifications);
        $this->assertNotContains('unclassified-public', $classifications);
    }

    public function test_notification_center_routes_are_explicit_authenticated_self_service(): void
    {
        $report = $this->inventoryReport('authorization-notification-center.json', strict: true);
        $routes = collect($report['routes'])->keyBy('name');

        foreach ([
            'notification-center.index',
            'notification-center.read-all',
            'notification-center.read',
            'notification-center.open',
            'notification-center.destroy',
        ] as $routeName) {
            $this->assertTrue($routes->has($routeName), 'Missing authorization inventory route '.$routeName.'.');
            $this->assertTrue((bool) $routes[$routeName]['authenticated']);
            $this->assertSame('self-service', $routes[$routeName]['classification']);
            $this->assertArrayHasKey($routeName, $report['allowlists']['self_service_routes']);
        }
    }

    public function test_inventory_exports_role_permission_matrix_without_personal_user_data(): void
    {
        $report = $this->inventoryReport('authorization-matrix.json', strict: true);

        foreach ([
            'super-administrator',
            'branch-administrator',
            'branch-manager',
            'loan-officer',
            'loan-inspector',
            'cashier',
            'accountant',
            'debt-collector',
            'hr-payroll-officer',
            'auditor',
        ] as $roleName) {
            $this->assertArrayHasKey($roleName, $report['role_permission_matrix']);
        }

        foreach ($report['roles'] as $role) {
            $this->assertSame([
                'name',
                'display_name',
                'description',
                'is_system',
                'users_count',
                'permissions',
            ], array_keys($role));
        }

        $this->assertArrayNotHasKey('users', $report);
        $this->assertArrayNotHasKey('staff', $report);
    }

    public function test_deprecated_broad_permissions_are_explicitly_frozen(): void
    {
        $report = $this->inventoryReport('authorization-legacy.json');

        $this->assertSame([
            'appointments.manage',
            'collections.manage',
            'customers.manage',
            'expenses.manage',
            'management-alerts.manage',
            'payroll.manage',
            'tasks.manage',
        ], collect(array_keys($report['legacy_broad_permissions']))->sort()->values()->all());
    }

    public function test_inventory_discovers_permission_and_role_name_call_sites(): void
    {
        $report = $this->inventoryReport('authorization-call-sites.json');
        $permissionKinds = array_column($report['authorization_call_sites']['permission'], 'kind');
        $roleKinds = array_column($report['authorization_call_sites']['role_name'], 'kind');

        $this->assertContains('permission_middleware', $permissionKinds);
        $this->assertContains('has_role', $roleKinds);
    }

    /** @return array<string, mixed> */
    private function inventoryReport(string $filename, bool $strict = false): array
    {
        $path = storage_path('framework/testing/'.$filename);
        File::delete($path);

        try {
            $arguments = ['--output' => $path];
            if ($strict) {
                $arguments['--strict'] = true;
            }

            $exitCode = Artisan::call('authorization:inventory', $arguments);

            $this->assertSame(0, $exitCode, Artisan::output());
            $this->assertFileExists($path);

            return json_decode(File::get($path), true, flags: JSON_THROW_ON_ERROR);
        } finally {
            File::delete($path);
        }
    }
}
