<?php
namespace App\Controllers;

use App\Core\Auth;
use App\Core\Controller;
use App\Core\Database;
use App\Core\Permission;
use PDO;
use RuntimeException;
use Throwable;

final class SecurityAuthorizationController extends Controller
{
    private PDO $pdo;

    public function __construct()
    {
        $this->pdo = Database::connection();
    }

    public function index(): void
    {
        Auth::requireLogin();
        Permission::require('administration.permissions.view');

        $organisationId = $this->organisationId();
        $rolesStmt = $this->pdo->prepare(
            "SELECT
                r.id,
                r.code,
                r.libelle,
                r.actif,
                COUNT(DISTINCT rp.permission_id) AS permission_count,
                COUNT(DISTINCT ur.utilisateur_id) AS user_count
             FROM roles r
             LEFT JOIN role_permissions rp ON rp.role_id = r.id
             LEFT JOIN utilisateur_roles ur ON ur.role_id = r.id
             WHERE r.deleted_at IS NULL AND r.organisation_id=:organisation_id
             GROUP BY r.id
             ORDER BY r.libelle"
        );
        $rolesStmt->execute(['organisation_id' => $organisationId]);
        $roles = $rolesStmt->fetchAll(PDO::FETCH_ASSOC);

        $usersStmt = $this->pdo->prepare(
            "SELECT
                u.id,
                u.nom,
                u.prenom,
                u.email,
                u.actif,
                GROUP_CONCAT(DISTINCT r.libelle ORDER BY r.libelle SEPARATOR ', ') AS roles,
                COUNT(DISTINCT up.permission_id) AS override_count
             FROM utilisateurs u
             LEFT JOIN utilisateur_roles ur ON ur.utilisateur_id = u.id
             LEFT JOIN roles r ON r.id = ur.role_id AND r.deleted_at IS NULL
             LEFT JOIN utilisateur_permissions up ON up.utilisateur_id = u.id
             WHERE u.deleted_at IS NULL AND u.organisation_id=:organisation_id
             GROUP BY u.id
             ORDER BY u.nom, u.prenom"
        );
        $usersStmt->execute(['organisation_id' => $organisationId]);
        $users = $usersStmt->fetchAll(PDO::FETCH_ASSOC);

        $this->view('security/index', [
            'pageTitle' => 'Sécurité & autorisations',
            'roles' => $roles,
            'users' => $users,
        ]);
    }

    public function role(): void
    {
        Auth::requireLogin();
        Permission::require('administration.permissions.view');

        $roleId = (int)($_GET['id'] ?? 0);
        $role = $this->findRole($roleId);
        $permissions = $this->permissionsMatrix();

        $stmt = $this->pdo->prepare(
            "SELECT permission_id
             FROM role_permissions
             WHERE role_id = :role_id"
        );
        $stmt->execute(['role_id' => $roleId]);

        $selected = array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));

        $this->view('security/role', [
            'pageTitle' => 'Autorisations du rôle',
            'role' => $role,
            'permissions' => $permissions,
            'selected' => $selected,
        ]);
    }

    public function saveRole(): void
    {
        Auth::requireLogin();
        Permission::require('administration.permissions.manage');
        Permission::requireSuperAdmin();
        Auth::requireRecentAuthentication();
        $this->verifyCsrf();

        $roleId = (int)($_POST['role_id'] ?? 0);
        $role = $this->findRole($roleId);
        if (in_array((string)$role['code'], ['super_admin', 'superadmin'], true)) {
            http_response_code(403);
            exit('Le rôle super-administrateur est protégé et ne peut pas être modifié ici.');
        }
        $permissionIds = array_values(array_unique(array_filter(
            array_map('intval', (array)($_POST['permissions'] ?? [])),
            static fn(int $id): bool => $id > 0
        )));

        $before = $this->rolePermissionIds($roleId);
        $actorId = $this->currentUserId();

        $this->pdo->beginTransaction();

        try {
            $stmt = $this->pdo->prepare(
                "DELETE FROM role_permissions WHERE role_id = :role_id"
            );
            $stmt->execute(['role_id' => $roleId]);

            if ($permissionIds) {
                $insert = $this->pdo->prepare(
                    "INSERT INTO role_permissions(role_id, permission_id)
                     SELECT :role_id, id
                     FROM permissions
                     WHERE id = :permission_id
                       AND actif = 1"
                );

                foreach ($permissionIds as $permissionId) {
                    $insert->execute([
                        'role_id' => $roleId,
                        'permission_id' => $permissionId,
                    ]);
                }
            }

            $affected = $this->pdo->prepare(
                "SELECT utilisateur_id FROM utilisateur_roles WHERE role_id=:role_id"
            );
            $affected->execute(['role_id' => $roleId]);
            $affectedIds = array_map('intval', $affected->fetchAll(PDO::FETCH_COLUMN));
            if ($affectedIds) {
                $placeholders = implode(',', array_fill(0, count($affectedIds), '?'));
                $this->pdo->prepare(
                    "UPDATE utilisateurs SET security_version=security_version+1 WHERE id IN ($placeholders)"
                )->execute($affectedIds);
                $this->pdo->prepare(
                    "UPDATE cda_user_sessions SET revoked_at=NOW()
                     WHERE utilisateur_id IN ($placeholders) AND revoked_at IS NULL"
                )->execute($affectedIds);
            }

            $this->audit(
                $actorId,
                'role',
                $roleId,
                'permissions.update',
                ['role' => $role['code'], 'permissions' => $before],
                ['role' => $role['code'], 'permissions' => $permissionIds]
            );

            $this->pdo->commit();
        } catch (Throwable $error) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $error;
        }

        $_SESSION['success'] = 'Autorisations du rôle enregistrées.';
        $this->redirect('/administration/securite/role?id=' . $roleId);
    }

    public function user(): void
    {
        Auth::requireLogin();
        Permission::require('administration.permissions.view');

        $userId = (int)($_GET['id'] ?? 0);
        $account = $this->findUser($userId);
        $permissions = $this->permissionsMatrix();

        $stmt = $this->pdo->prepare(
            "SELECT permission_id, accordee, perimetre
             FROM utilisateur_permissions
             WHERE utilisateur_id = :user_id"
        );
        $stmt->execute(['user_id' => $userId]);

        $overrides = [];
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $overrides[(int)$row['permission_id']] = [
                'accordee' => (int)$row['accordee'],
                'perimetre' => (string)$row['perimetre'],
            ];
        }

        $inherited = $this->inheritedPermissionIds($userId);

        $this->view('security/user', [
            'pageTitle' => 'Autorisations individuelles',
            'account' => $account,
            'permissions' => $permissions,
            'overrides' => $overrides,
            'inherited' => $inherited,
        ]);
    }

    public function saveUser(): void
    {
        Auth::requireLogin();
        Permission::require('administration.permissions.manage');
        Permission::requireSuperAdmin();
        Auth::requireRecentAuthentication();
        $this->verifyCsrf();

        $userId = (int)($_POST['user_id'] ?? 0);
        $account = $this->findUser($userId);
        $actorId = $this->currentUserId();
        if ($userId === $actorId) {
            http_response_code(403);
            exit('Il est interdit de modifier ses propres autorisations.');
        }
        $states = (array)($_POST['permission_state'] ?? []);
        $scopes = (array)($_POST['permission_scope'] ?? []);

        $before = $this->userOverrides($userId);
        $allowedScopes = [
            'global',
            'organisation',
            'ses_ateliers',
            'ses_groupes',
            'ses_seances',
            'dossiers_affectes',
        ];

        $this->pdo->beginTransaction();

        try {
            $delete = $this->pdo->prepare(
                "DELETE FROM utilisateur_permissions WHERE utilisateur_id = :user_id"
            );
            $delete->execute(['user_id' => $userId]);

            $insert = $this->pdo->prepare(
                "INSERT INTO utilisateur_permissions(
                    utilisateur_id,
                    permission_id,
                    accordee,
                    perimetre,
                    created_at,
                    updated_at
                 )
                 SELECT
                    :user_id,
                    id,
                    :accordee,
                    :perimetre,
                    NOW(),
                    NOW()
                 FROM permissions
                 WHERE id = :permission_id
                   AND actif = 1"
            );

            foreach ($states as $permissionId => $state) {
                $permissionId = (int)$permissionId;
                $state = (string)$state;

                if ($permissionId <= 0 || !in_array($state, ['allow', 'deny'], true)) {
                    continue;
                }

                $scope = (string)($scopes[$permissionId] ?? 'global');
                if (!in_array($scope, $allowedScopes, true)) {
                    $scope = 'global';
                }

                $insert->execute([
                    'user_id' => $userId,
                    'permission_id' => $permissionId,
                    'accordee' => $state === 'allow' ? 1 : 0,
                    'perimetre' => $scope,
                ]);
            }

            $after = $this->userOverrides($userId);
            $this->pdo->prepare(
                "UPDATE utilisateurs SET security_version=security_version+1 WHERE id=:id"
            )->execute(['id' => $userId]);
            $this->pdo->prepare(
                "UPDATE cda_user_sessions SET revoked_at=NOW()
                 WHERE utilisateur_id=:id AND revoked_at IS NULL"
            )->execute(['id' => $userId]);

            $this->audit(
                $actorId,
                'utilisateur',
                $userId,
                'permissions.override',
                [
                    'utilisateur' => $account['email'],
                    'exceptions' => $before,
                ],
                [
                    'utilisateur' => $account['email'],
                    'exceptions' => $after,
                ]
            );

            $this->pdo->commit();
        } catch (Throwable $error) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $error;
        }

        $_SESSION['success'] = 'Autorisations individuelles enregistrées.';
        $this->redirect('/administration/securite/utilisateur?id=' . $userId);
    }

    public function auditLog(): void
    {
        Auth::requireLogin();
        Permission::require('administration.permissions.view');

        $stmt = $this->pdo->prepare(
            "SELECT
                a.*,
                CONCAT(COALESCE(u.prenom, ''), ' ', COALESCE(u.nom, '')) AS auteur
             FROM autorisations_audit a
             LEFT JOIN utilisateurs u ON u.id = a.auteur_utilisateur_id
             WHERE u.organisation_id=:organisation_id
             ORDER BY a.created_at DESC, a.id DESC
             LIMIT 200"
        );
        $stmt->execute(['organisation_id' => $this->organisationId()]);
        $entries = $stmt->fetchAll(PDO::FETCH_ASSOC);

        $this->view('security/audit', [
            'pageTitle' => 'Journal des autorisations',
            'entries' => $entries,
        ]);
    }

    private function permissionsMatrix(): array
    {
        $rows = $this->pdo->query(
            "SELECT id, code, module, action, libelle
             FROM permissions
             WHERE actif = 1
             ORDER BY module, libelle"
        )->fetchAll(PDO::FETCH_ASSOC);

        $matrix = [];
        foreach ($rows as $row) {
            $matrix[(string)$row['module']][] = $row;
        }

        return $matrix;
    }

    private function findRole(int $id): array
    {
        $stmt = $this->pdo->prepare(
            "SELECT id, code, libelle, actif
             FROM roles
             WHERE id = :id
               AND organisation_id = :organisation_id
               AND deleted_at IS NULL"
        );
        $stmt->execute(['id' => $id, 'organisation_id' => $this->organisationId()]);
        $role = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$role) {
            http_response_code(404);
            exit('Rôle introuvable.');
        }

        return $role;
    }

    private function findUser(int $id): array
    {
        $stmt = $this->pdo->prepare(
            "SELECT id, nom, prenom, email, actif
             FROM utilisateurs
             WHERE id = :id
               AND organisation_id = :organisation_id
               AND deleted_at IS NULL"
        );
        $stmt->execute(['id' => $id, 'organisation_id' => $this->organisationId()]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$user) {
            http_response_code(404);
            exit('Utilisateur introuvable.');
        }

        return $user;
    }

    private function rolePermissionIds(int $roleId): array
    {
        $stmt = $this->pdo->prepare(
            "SELECT permission_id
             FROM role_permissions
             WHERE role_id = :role_id
             ORDER BY permission_id"
        );
        $stmt->execute(['role_id' => $roleId]);
        return array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));
    }

    private function inheritedPermissionIds(int $userId): array
    {
        $stmt = $this->pdo->prepare(
            "SELECT DISTINCT rp.permission_id
             FROM utilisateur_roles ur
             INNER JOIN roles r
                     ON r.id = ur.role_id
                    AND r.actif = 1
                    AND r.deleted_at IS NULL
             INNER JOIN role_permissions rp ON rp.role_id = r.id
             INNER JOIN permissions p
                     ON p.id = rp.permission_id
                    AND p.actif = 1
             WHERE ur.utilisateur_id = :user_id"
        );
        $stmt->execute(['user_id' => $userId]);
        return array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));
    }

    private function userOverrides(int $userId): array
    {
        $stmt = $this->pdo->prepare(
            "SELECT permission_id, accordee, perimetre
             FROM utilisateur_permissions
             WHERE utilisateur_id = :user_id
             ORDER BY permission_id"
        );
        $stmt->execute(['user_id' => $userId]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    private function audit(
        int $actorId,
        string $targetType,
        int $targetId,
        string $action,
        array $before,
        array $after
    ): void {
        $stmt = $this->pdo->prepare(
            "INSERT INTO autorisations_audit(
                auteur_utilisateur_id,
                cible_type,
                cible_id,
                action,
                valeur_avant,
                valeur_apres,
                ip_address,
                user_agent,
                created_at
             ) VALUES(
                :actor_id,
                :target_type,
                :target_id,
                :action,
                :before_value,
                :after_value,
                :ip_address,
                :user_agent,
                NOW()
             )"
        );

        $stmt->execute([
            'actor_id' => $actorId ?: null,
            'target_type' => $targetType,
            'target_id' => $targetId,
            'action' => $action,
            'before_value' => json_encode($before, JSON_UNESCAPED_UNICODE),
            'after_value' => json_encode($after, JSON_UNESCAPED_UNICODE),
            'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
        ]);
    }

    private function verifyCsrf(): void
    {
        $token = (string)($_POST['_csrf'] ?? '');
        $session = (string)($_SESSION['_csrf'] ?? '');

        if ($session === '' || !hash_equals($session, $token)) {
            throw new RuntimeException('Jeton de sécurité invalide.');
        }
    }

    private function currentUserId(): int
    {
        foreach ([
            $_SESSION['utilisateur_id'] ?? null,
            $_SESSION['user_id'] ?? null,
            $_SESSION['utilisateur']['id'] ?? null,
            $_SESSION['user']['id'] ?? null,
        ] as $id) {
            if (is_numeric($id) && (int)$id > 0) {
                return (int)$id;
            }
        }

        return 0;
    }

    private function organisationId(): int
    {
        return (int)(Auth::user()['organisation_id'] ?? 0);
    }
}
