<?php
declare(strict_types=1);
namespace App\Services;

use App\Core\Database;
use RuntimeException;

final class SecuritySessionControlService
{
    /** @return array<string,mixed> */
    public function dashboard(): array
    {
        $sql = "SELECT u.id, u.nom, u.prenom, u.email, u.statut,
                       COUNT(s.session_token_hash) AS active_sessions,
                       MAX(s.last_seen_at) AS last_seen_at,
                       MIN(s.created_at) AS oldest_session_at
                FROM utilisateurs u
                LEFT JOIN cda_user_sessions s
                  ON s.utilisateur_id=u.id
                 AND s.revoked_at IS NULL
                 AND s.expires_at>NOW()
                WHERE u.deleted_at IS NULL
                GROUP BY u.id,u.nom,u.prenom,u.email,u.statut
                HAVING active_sessions>0
                ORDER BY active_sessions DESC, u.nom, u.prenom";
        $stmt = Database::connection()->prepare($sql);
        $stmt->execute();
        $rows = $stmt->fetchAll();
        $total = 0;
        foreach ($rows as &$row) {
            $row['active_sessions'] = (int)$row['active_sessions'];
            $total += $row['active_sessions'];
            $row['email_masked'] = $this->maskEmail((string)$row['email']);
            unset($row['email']);
        }
        unset($row);
        return [
            'users'=>$rows,
            'active_sessions'=>$total,
            'active_users'=>count($rows),
            'generated_at'=>date('Y-m-d H:i:s'),
        ];
    }

    public function revokeUser(int $targetUserId, int $actorUserId): bool
    {
        if ($targetUserId <= 0) throw new RuntimeException('Utilisateur invalide.');
        $pdo = Database::connection();
        $pdo->beginTransaction();
        try {
            $stmt = $pdo->prepare("UPDATE cda_user_sessions SET revoked_at=NOW() WHERE utilisateur_id=:id AND revoked_at IS NULL");
            $stmt->execute(['id'=>$targetUserId]);
            $count = $stmt->rowCount();
            $pdo->prepare("UPDATE utilisateurs SET security_version=security_version+1 WHERE id=:id AND deleted_at IS NULL")
                ->execute(['id'=>$targetUserId]);
            $pdo->commit();
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            throw $e;
        }
        SecurityEventMonitor::record('security.sessions.user_revoked', 'warning', [
            'target_user_hash'=>hash('sha256', (string)$targetUserId),
            'actor_user_id'=>$actorUserId,
            'revoked_count'=>$count,
        ]);
        return $targetUserId === $actorUserId;
    }

    public function revokeAllExceptCurrent(int $actorUserId, string $currentToken): int
    {
        if ($actorUserId <= 0 || $currentToken === '') throw new RuntimeException('Session courante invalide.');
        $hash = hash('sha256', $currentToken);
        $stmt = Database::connection()->prepare(
            "UPDATE cda_user_sessions SET revoked_at=NOW()
             WHERE revoked_at IS NULL AND expires_at>NOW() AND session_token_hash<>:token"
        );
        $stmt->execute(['token'=>$hash]);
        $count = $stmt->rowCount();
        SecurityEventMonitor::record('security.sessions.global_revocation', 'critical', [
            'actor_user_id'=>$actorUserId,
            'revoked_count'=>$count,
            'current_session_preserved'=>true,
        ]);
        return $count;
    }

    private function maskEmail(string $email): string
    {
        [$local,$domain] = array_pad(explode('@',$email,2),2,'');
        if ($domain === '') return 'Compte interne';
        $visible = mb_substr($local,0,1);
        return $visible . str_repeat('•', max(3, min(8, mb_strlen($local)-1))) . '@' . $domain;
    }
}
