<?php

namespace App\Console\Commands;

use App\Models\Permission;
use App\Models\Role;
use App\Support\Authorization\DefaultRoleMatrix;
use App\Support\Authorization\EffectiveRoleMatrix;
use App\Support\Authorization\PermissionCatalog;
use App\Support\Authorization\PermissionName;
use Illuminate\Console\Command;

class AuthorizationCatalogAudit extends Command
{
    protected $signature = 'authorization:catalog-check {--json : Emit the catalog integrity report as JSON}';

    protected $description = 'Validate the canonical permission catalog, default role matrix, and seeded Spatie authorization state';

    public function handle(): int
    {
        $errors = [];
        $warnings = [];
        $definitions = PermissionCatalog::definitions();
        $catalogNames = array_column($definitions, 'name');
        $enumNames = PermissionName::values();
        $allowedRisks = [
            PermissionCatalog::RISK_READ,
            PermissionCatalog::RISK_OPERATE,
            PermissionCatalog::RISK_APPROVE,
            PermissionCatalog::RISK_FINANCIAL,
            PermissionCatalog::RISK_SECURITY,
        ];

        if (count($catalogNames) !== count(array_unique($catalogNames))) {
            $errors[] = 'Permission catalog contains duplicate permission names.';
        }

        if ($this->sorted($catalogNames) !== $this->sorted($enumNames)) {
            $errors[] = 'PermissionName enum values and PermissionCatalog definitions are not identical.';
        }

        foreach ($definitions as $definition) {
            if (! in_array($definition['risk'], $allowedRisks, true)) {
                $errors[] = "Permission {$definition['name']} has unsupported risk level {$definition['risk']}.";
            }

            if ($definition['deprecated']) {
                if (blank($definition['deprecation_reason'])) {
                    $errors[] = "Deprecated permission {$definition['name']} has no deprecation reason.";
                }

                foreach ($definition['replacement'] as $replacement) {
                    if (! in_array($replacement, $catalogNames, true)) {
                        $errors[] = "Deprecated permission {$definition['name']} references unknown replacement {$replacement}.";
                    }
                }
            }
        }

        $defaultRoles = DefaultRoleMatrix::roles();
        foreach ($defaultRoles as $roleName => $definition) {
            if (count($definition['permissions']) !== count(array_unique($definition['permissions']))) {
                $errors[] = "Default role {$roleName} contains duplicate permissions.";
            }

            foreach ($definition['permissions'] as $permission) {
                if (! in_array($permission, $catalogNames, true)) {
                    $errors[] = "Default role {$roleName} references unknown permission {$permission}.";
                }
            }
        }

        $effectiveRoles = EffectiveRoleMatrix::roles();
        foreach ($effectiveRoles as $roleName => $definition) {
            foreach ($definition['permissions'] as $permission) {
                if (! in_array($permission, $catalogNames, true)) {
                    $errors[] = "Effective role {$roleName} references unknown permission {$permission}.";
                }
            }
        }

        $databasePermissions = Permission::query()->orderBy('name')->get()->keyBy('name');
        $catalogByName = PermissionCatalog::byName();

        foreach ($catalogByName as $name => $definition) {
            $permission = $databasePermissions->get($name);
            if (! $permission) {
                $errors[] = "Catalog permission {$name} is missing from the database after seeding.";
                continue;
            }

            if ($permission->guard_name !== PermissionCatalog::GUARD) {
                $errors[] = "Permission {$name} uses guard {$permission->guard_name} instead of ".PermissionCatalog::GUARD.'.';
            }
            if ($permission->display_name !== $definition['display_name']) {
                $errors[] = "Permission {$name} display name differs from the catalog.";
            }
            if ($permission->group !== $definition['group']) {
                $errors[] = "Permission {$name} group differs from the catalog.";
            }
            if ($definition['description'] !== null && $permission->description !== $definition['description']) {
                $errors[] = "Permission {$name} description differs from the catalog.";
            }
        }

        foreach ($databasePermissions->keys()->diff($catalogNames) as $unknownPermission) {
            $warnings[] = "Database permission {$unknownPermission} is not owned by the canonical application catalog.";
        }

        $databaseRoles = Role::query()->with('permissions:id,name')->get()->keyBy('name');
        foreach ($effectiveRoles as $roleName => $definition) {
            $role = $databaseRoles->get($roleName);
            if (! $role) {
                $errors[] = "Default system role {$roleName} is missing from the database after seeding.";
                continue;
            }

            if (! $role->is_system) {
                $errors[] = "Default role {$roleName} is not marked as a system role.";
            }
            if ($role->guard_name !== PermissionCatalog::GUARD) {
                $errors[] = "Role {$roleName} uses guard {$role->guard_name} instead of ".PermissionCatalog::GUARD.'.';
            }
            if ($role->display_name !== $definition['display_name']) {
                $errors[] = "Role {$roleName} display name differs from the effective role matrix.";
            }
            if ($definition['description'] !== null && $role->description !== $definition['description']) {
                $errors[] = "Role {$roleName} description differs from the effective role matrix.";
            }

            $actualPermissions = $role->permissions->pluck('name')->sort()->values()->all();
            $expectedPermissions = $this->sorted($definition['permissions']);
            if ($actualPermissions !== $expectedPermissions) {
                $errors[] = "Role {$roleName} permission grants differ from the effective role matrix.";
            }
        }

        $report = [
            'status' => $errors === [] ? 'pass' : 'fail',
            'guard' => PermissionCatalog::GUARD,
            'summary' => [
                'catalog_permissions' => count($catalogNames),
                'deprecated_permissions' => count(PermissionCatalog::legacyPermissions()),
                'default_roles' => count($defaultRoles),
                'errors' => count($errors),
                'warnings' => count($warnings),
            ],
            'default_role_permission_counts' => collect($defaultRoles)
                ->map(fn (array $definition): int => count($definition['permissions']))
                ->all(),
            'effective_role_permission_counts' => collect($effectiveRoles)
                ->map(fn (array $definition): int => count($definition['permissions']))
                ->all(),
            'errors' => $errors,
            'warnings' => $warnings,
        ];

        if ($this->option('json')) {
            $this->line(json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR));
        } else {
            $this->info('Authorization canonical catalog: '.$report['status']);
            $this->line('Catalog permissions: '.$report['summary']['catalog_permissions']);
            $this->line('Deprecated permissions: '.$report['summary']['deprecated_permissions']);
            $this->line('Default roles: '.$report['summary']['default_roles']);

            foreach ($errors as $error) {
                $this->error($error);
            }
            foreach ($warnings as $warning) {
                $this->warn($warning);
            }
        }

        return $errors === [] ? self::SUCCESS : self::FAILURE;
    }

    /** @param array<int, string> $values @return array<int, string> */
    private function sorted(array $values): array
    {
        sort($values);

        return array_values($values);
    }
}
