<?php

declare(strict_types=1);

final class TrustedDeviceService
{
    private const COOKIE = 'globinours_device';
    public static function create(int $userId, string $name = ''): void
    {
        $selector = bin2hex(random_bytes(9));
        $token = random_bytes(32);
        $expires = time() + 60 * 60 * 24 * 90;
        $name = trim($name) ?: self::deviceLabel();
        DB::pdo()
            ->prepare(
                'INSERT INTO trusted_devices(user_id,selector,token_hash,device_name,user_agent,ip_address,expires_at) VALUES(?,?,?,?,?,?,?)',
            )
            ->execute([
                $userId,
                $selector,
                hash('sha256', $token),
                mb_substr($name, 0, 100),
                mb_substr((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 500) ?: null,
                $_SERVER['REMOTE_ADDR'] ?? null,
                date('Y-m-d H:i:s', $expires),
            ]);
        self::cookie($selector . '.' . bin2hex($token), $expires);
    }
    public static function authenticate(): ?int
    {
        $raw = (string) ($_COOKIE[self::COOKIE] ?? '');
        if (!preg_match('/^([a-f0-9]{18})\.([a-f0-9]{64})$/', $raw, $m)) {
            return null;
        }
        try {
            $s = DB::pdo()->prepare(
                "SELECT td.*,u.active FROM trusted_devices td JOIN users u ON u.id=td.user_id WHERE td.selector=? AND td.revoked_at IS NULL AND datetime(td.expires_at)>datetime('now') AND (td.last_used_at IS NULL OR datetime(td.last_used_at)>datetime('now','-30 days'))",
            );
            $s->execute([$m[1]]);
            $row = $s->fetch(PDO::FETCH_ASSOC);
            if (
                !$row ||
                !hash_equals((string) $row['token_hash'], hash('sha256', hex2bin($m[2]) ?: '')) ||
                (int) $row['active'] !== 1
            ) {
                self::forget();
                return null;
            }
            $new = random_bytes(32);
            DB::pdo()
                ->prepare(
                    "UPDATE trusted_devices SET token_hash=?,last_used_at=datetime('now'),ip_address=? WHERE id=?",
                )
                ->execute([hash('sha256', $new), $_SERVER['REMOTE_ADDR'] ?? null, $row['id']]);
            self::cookie($m[1] . '.' . bin2hex($new), strtotime((string) $row['expires_at']));
            return (int) $row['user_id'];
        } catch (Throwable) {
            return null;
        }
    }
    public static function currentSelector(): ?string
    {
        $raw = (string) ($_COOKIE[self::COOKIE] ?? '');
        return preg_match('/^([a-f0-9]{18})\./', $raw, $m) ? $m[1] : null;
    }
    public static function revokeCurrent(): void
    {
        $selector = self::currentSelector();
        if ($selector) {
            try {
                DB::pdo()
                    ->prepare("UPDATE trusted_devices SET revoked_at=datetime('now') WHERE selector=?")
                    ->execute([$selector]);
            } catch (Throwable) {
            }
        }
        self::forget();
    }
    public static function devices(?int $userId = null): array
    {
        $userId ??= Auth::id();
        $s = DB::pdo()->prepare(
            'SELECT * FROM trusted_devices WHERE user_id=? ORDER BY revoked_at IS NULL DESC,last_used_at DESC',
        );
        $s->execute([$userId]);
        return $s->fetchAll(PDO::FETCH_ASSOC);
    }
    public static function revoke(int $id, int $userId, bool $admin = false): void
    {
        $sql =
            'UPDATE trusted_devices SET revoked_at=datetime(\'now\') WHERE id=:id' .
            ($admin ? '' : ' AND user_id=:uid');
        $params = [':id' => $id];
        if (!$admin) {
            $params[':uid'] = $userId;
        }
        DB::pdo()->prepare($sql)->execute($params);
        if (
            self::currentSelector() &&
            self::currentSelector() ===
                (string) DB::pdo()
                    ->query('SELECT selector FROM trusted_devices WHERE id=' . (int) $id)
                    ->fetchColumn()
        ) {
            self::forget();
        }
    }
    private static function deviceLabel(): string
    {
        $ua = strtolower((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''));
        if (str_contains($ua, 'globinoursandroid')) {
            return 'Application Android';
        }
        if (str_contains($ua, 'android')) {
            return 'Smartphone ou tablette Android';
        }
        if (str_contains($ua, 'iphone') || str_contains($ua, 'ipad')) {
            return 'iPhone ou iPad';
        }
        return 'Navigateur web';
    }
    private static function cookie(string $value, int $expires): void
    {
        setcookie(self::COOKIE, $value, [
            'expires' => $expires,
            'path' => '/',
            'secure' => Auth::isSecureRequest(),
            'httponly' => true,
            'samesite' => 'Strict',
        ]);
        $_COOKIE[self::COOKIE] = $value;
    }
    private static function forget(): void
    {
        setcookie(self::COOKIE, '', [
            'expires' => time() - 3600,
            'path' => '/',
            'secure' => Auth::isSecureRequest(),
            'httponly' => true,
            'samesite' => 'Strict',
        ]);
        unset($_COOKIE[self::COOKIE]);
    }
}
