<?php

declare(strict_types=1);

final class PermissionService
{
    public const ROLES = [
        'admin' => 'Administrateur',
        'responsable' => 'Responsable',
        'benevole' => 'Bénévole',
        'lecture' => 'Lecture seule',
    ];
    public const MODULES = [
        'dashboard' => ['Dashboard', 'Priorités et situation quotidienne'],
        'animals' => ['Animaux', 'Fiches, portées, liens, médias et adoptions'],
        'medical' => ['Médical', 'Notes, traitements, vaccinations et décès'],
        'care' => ['Tournées', 'Passages tablette et configuration des box'],
        'directory' => ['Annuaire', 'Personnes, structures et coordonnées'],
        'agenda' => ['Agenda', 'Rendez-vous, transports, visites et tâches partagées'],
        'statistics' => ['Statistiques', 'Analyses annuelles et tendances'],
        'administrative' => ['Administratif', 'Registre, documents et exports'],
        'accounting' => ['Comptabilité', 'Factures, avoirs et règlements'],
        'grants' => ['Subventions', 'Bilans, demandes et justificatifs'],
    ];
    public static function roleLabel(string $key): string
    {
        return t('permission.role.' . $key, [], self::ROLES[$key] ?? $key);
    }
    public static function moduleLabel(string $key): string
    {
        return t('permission.module.' . $key, [], self::MODULES[$key][0] ?? $key);
    }
    public static function moduleDescription(string $key): string
    {
        return t('permission.module_help.' . $key, [], self::MODULES[$key][1] ?? '');
    }

    private static array $cache = [];
    public static function can(string $module, string $action = 'view', ?string $role = null): bool
    {
        $role ??= (string) (Auth::user()['role'] ?? '');
        if ($role === 'admin') {
            return true;
        }
        if (!isset(self::MODULES[$module], self::ROLES[$role])) {
            return false;
        }
        if (!isset(self::$cache[$role])) {
            $s = DB::pdo()->prepare('SELECT module,can_view,can_edit FROM role_permissions WHERE role=?');
            $s->execute([$role]);
            self::$cache[$role] = [];
            foreach ($s->fetchAll(PDO::FETCH_ASSOC) as $row) {
                self::$cache[$role][$row['module']] = [
                    'view' => (bool) $row['can_view'],
                    'edit' => (bool) $row['can_edit'],
                ];
            }
        }
        return (bool) (self::$cache[$role][$module][$action === 'edit' ? 'edit' : 'view'] ?? false);
    }
    public static function matrix(): array
    {
        $matrix = [];
        foreach (array_keys(self::ROLES) as $role) {
            foreach (array_keys(self::MODULES) as $module) {
                $matrix[$role][$module] = [
                    'view' => self::can($module, 'view', $role),
                    'edit' => self::can($module, 'edit', $role),
                ];
            }
        }
        return $matrix;
    }
    public static function moduleForPath(string $path): ?string
    {
        if ($path === '/') {
            return 'dashboard';
        }
        if (str_starts_with($path, '/animal/documents')) {
            return 'medical';
        }
        if (
            str_starts_with($path, '/animals') ||
            str_starts_with($path, '/animal') ||
            str_starts_with($path, '/litter') ||
            str_starts_with($path, '/bonded')
        ) {
            foreach (
                [
                    '/animal/add-medical',
                    '/animal/medical-status',
                    '/animal/add-treatment',
                    '/animal/add-vaccine',
                    '/animal/death',
                    '/animal/prescription',
                    '/animal/lab-report',
                    '/animal/medical-document',
                    '/animal/surgery',
                ]
                as $medical
            ) {
                if (str_starts_with($path, $medical)) {
                    return 'medical';
                }
            }
            return 'animals';
        }
        if (str_starts_with($path, '/dashboard')) {
            return 'dashboard';
        }
        if (str_starts_with($path, '/care-round')) {
            return 'care';
        }
        if (str_starts_with($path, '/directory')) {
            return 'directory';
        }
        if (str_starts_with($path, '/agenda')) {
            return 'agenda';
        }
        if (str_starts_with($path, '/statistics')) {
            return 'statistics';
        }
        if (str_starts_with($path, '/accounting')) {
            return 'accounting';
        }
        if (str_starts_with($path, '/admin/grants')) {
            return 'grants';
        }
        if (str_starts_with($path, '/admin') || str_starts_with($path, '/documents')) {
            return 'administrative';
        }
        return null;
    }
    public static function enforce(string $path, string $method): void
    {
        if (
            str_starts_with($path, '/settings') ||
            str_starts_with($path, '/dev/') ||
            in_array($path, ['/logout', '/evolutions'], true)
        ) {
            return;
        }
        $module = self::moduleForPath($path);
        if (!$module) {
            return;
        }
        $action = strtoupper($method) === 'POST' || self::isEditPage($path) ? 'edit' : 'view';
        $allowed = self::can($module, $action) && ($module !== 'medical' || self::can('animals', 'view'));
        if (!$allowed) {
            AuditService::log('authorization_denied', $path, 'Accès refusé au module ' . $module, null, null, [
                'module' => $module,
                'requested_action' => $action,
            ]);
            http_response_code(403);
            if (function_exists('render')) {
                render('access_denied.php', [
                    'title' => t('access.denied'),
                    'deniedAction' => $action,
                    'deniedModule' => self::moduleLabel($module),
                ]);
            } else {
                echo t('access.denied');
            }
            exit();
        }
    }
    public static function firstAllowedPath(): string
    {
        foreach (
            [
                'dashboard' => '/dashboard',
                'animals' => '/animals',
                'care' => '/care-round',
                'directory' => '/directory',
                'agenda' => '/agenda',
                'statistics' => '/statistics',
                'accounting' => '/accounting',
                'administrative' => '/admin/register',
                'grants' => '/admin/grants',
            ]
            as $module => $path
        ) {
            if (self::can($module)) {
                return $path;
            }
        }
        return '/evolutions';
    }
    public static function canOpenPath(string $path): bool
    {
        $module = self::moduleForPath($path);
        return $module === null ||
            (self::can($module, self::isEditPage($path) ? 'edit' : 'view') &&
                ($module !== 'medical' || self::can('animals')));
    }
    public static function currentPermissions(): array
    {
        $out = [];
        foreach (self::MODULES as $module => $unused) {
            $out[$module] = ['view' => self::can($module), 'edit' => self::can($module, 'edit')];
        }
        return $out;
    }
    private static function isEditPage(string $path): bool
    {
        return in_array(
            $path,
            [
                '/animal/new',
                '/animal/edit',
                '/animal/death',
                '/animal/surgery/new',
                '/litter/new',
                '/litter/add-existing',
                '/litter/add-existing/save',
                '/bonded/new',
                '/directory/new',
                '/directory/edit',
                '/care-round/setup',
            ],
            true,
        );
    }
    public static function save(array $input, ?int $userId): void
    {
        $db = DB::pdo();
        $stmt = $db->prepare(
            "INSERT INTO role_permissions(role,module,can_view,can_edit,updated_by,updated_at) VALUES(:role,:module,:view,:edit,:user,datetime('now')) ON CONFLICT(role,module) DO UPDATE SET can_view=excluded.can_view,can_edit=excluded.can_edit,updated_by=excluded.updated_by,updated_at=excluded.updated_at",
        );
        $db->beginTransaction();
        try {
            foreach (self::ROLES as $role => $unused) {
                if ($role === 'admin') {
                    continue;
                }
                foreach (self::MODULES as $module => $unusedModule) {
                    $view = isset($input[$role][$module]['view']) ? 1 : 0;
                    if ($module === 'medical' && !isset($input[$role]['animals']['view'])) {
                        $view = 0;
                    }
                    $edit = $view && isset($input[$role][$module]['edit']) ? 1 : 0;
                    $stmt->execute([
                        ':role' => $role,
                        ':module' => $module,
                        ':view' => $view,
                        ':edit' => $edit,
                        ':user' => $userId,
                    ]);
                }
            }
            $db->commit();
            self::$cache = [];
        } catch (Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }
}
