<?php

namespace App\Services\Permission;

use App\Models\Permission;
use App\Models\Role;
use Illuminate\Support\Collection;
use Spatie\Permission\Models\Permission as SpatiePermission;
use Spatie\Permission\PermissionRegistrar;

class PermissionRegistry
{
    /**
     * Sync all configured module permissions into the database.
     *
     * @return list<string>
     */
    public function syncConfiguredPermissions(): array
    {
        app()[PermissionRegistrar::class]->forgetCachedPermissions();

        $guard = config('permissions.guard', 'web');
        $names = [];

        foreach ($this->flattenConfiguredPermissions() as $name => $label) {
            Permission::findOrCreate($name, $guard);
            $names[] = $name;
        }

        return $names;
    }

    /**
     * @return array<string, string>
     */
    public function flattenConfiguredPermissions(): array
    {
        $flat = [];

        foreach (config('permissions.modules', []) as $permissions) {
            foreach ($permissions as $name => $label) {
                $flat[$name] = $label;
            }
        }

        return $flat;
    }

    /**
     * Permissions grouped by module for the roles matrix UI.
     *
     * @return array<string, list<array{name: string, label: string, granted: array<string, bool>}>>
     */
    public function matrixForRole(?Role $role): array
    {
        $rolePermissionNames = $role
            ? $role->permissions->pluck('name')->flip()
            : collect();

        $groups = [];

        foreach (config('permissions.modules', []) as $module => $permissions) {
            $rows = [];

            foreach ($permissions as $name => $label) {
                $rows[] = [
                    'name' => $name,
                    'label' => $label,
                    'granted' => $this->grantedFlags($name, $rolePermissionNames->has($name)),
                ];
            }

            $groups[$module] = $rows;
        }

        return $groups;
    }

    /**
     * @return Collection<int, Permission>
     */
    public function allCached(): Collection
    {
        return Permission::query()
            ->forGuard(config('permissions.guard', 'web'))
            ->orderBy('name')
            ->get(['id', 'name', 'guard_name']);
    }

    /**
     * Register permissions for a module at runtime.
     *
     * @param  array<string, string>  $permissions
     */
    public function registerModule(string $module, array $permissions): void
    {
        if ($permissions === []) {
            return;
        }

        $guard = config('permissions.guard', 'web');

        foreach ($permissions as $name => $label) {
            SpatiePermission::findOrCreate($name, $guard);
        }

        $existingModules = config('permissions.modules', []);
        $existingModules[$module] = array_merge($existingModules[$module] ?? [], $permissions);
        config(['permissions.modules' => $existingModules]);
    }

    /**
     * Grant all module permissions to configured default install roles (e.g. Super Admin).
     *
     * @param  array<string, string>  $permissions
     */
    public function grantModulePermissionsToDefaultRoles(array $permissions): void
    {
        if ($permissions === []) {
            return;
        }

        app()[PermissionRegistrar::class]->forgetCachedPermissions();

        $guard = config('permissions.guard', 'web');
        $permissionNames = array_keys($permissions);

        foreach (config('permissions.default_module_install_roles', [config('permissions.super_admin_role')]) as $roleName) {
            $role = Role::findByName($roleName, $guard);
            $role?->givePermissionTo($permissionNames);
        }
    }

    /**
     * @param  list<string>  $permissionNames
     */
    public function revokeModulePermissions(array $permissionNames): void
    {
        if ($permissionNames === []) {
            return;
        }

        app()[PermissionRegistrar::class]->forgetCachedPermissions();

        $guard = config('permissions.guard', 'web');

        foreach ($permissionNames as $name) {
            $permission = Permission::query()
                ->where('name', $name)
                ->where('guard_name', $guard)
                ->first();

            if (! $permission) {
                continue;
            }

            $permission->roles()->detach();
            $permission->users()->detach();
        }
    }

    /**
     * @param  Collection<string, int>  $granted
     * @return array<string, bool>
     */
    private function grantedFlags(string $permissionName, bool $granted): array
    {
        $suffix = strtoupper(last(explode('_', $permissionName, 2)) ?: '');

        return [
            'view' => $granted && $suffix === 'VIEW',
            'create' => $granted && $suffix === 'CREATE',
            'edit' => $granted && in_array($suffix, ['UPDATE', 'EDIT'], true),
            'delete' => $granted && $suffix === 'DELETE',
            'all' => $granted,
        ];
    }
}
