<?php
namespace App\Core;

use PDO;

class MobileLicenseManager
{
    private const ACTIVATION_CODE_TTL_DAYS = 7;

    public static function ensureSchema(PDO $pdo): void
    {
        $pdo->exec("CREATE TABLE IF NOT EXISTS mobile_user_licenses (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_id INT NOT NULL,
            email_snapshot VARCHAR(190) NOT NULL,
            is_enabled TINYINT(1) NOT NULL DEFAULT 1,
            activation_code_hash CHAR(64) NULL,
            activation_code_hint VARCHAR(32) NULL,
            activation_code_expires_at DATETIME NULL,
            code_generated_at DATETIME NULL,
            code_generated_by INT NULL,
            first_activated_at DATETIME NULL,
            activated_at DATETIME NULL,
            activated_email VARCHAR(190) NULL,
            activated_device_id VARCHAR(191) NULL,
            activated_device_fingerprint VARCHAR(191) NULL,
            activated_platform VARCHAR(30) NULL,
            activated_model VARCHAR(120) NULL,
            activation_count INT NOT NULL DEFAULT 0,
            last_login_at DATETIME NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_mobile_license_user (user_id),
            KEY idx_mobile_license_email (email_snapshot),
            KEY idx_mobile_license_enabled (is_enabled),
            KEY idx_mobile_license_device (activated_device_id),
            KEY idx_mobile_license_fingerprint (activated_device_fingerprint)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        $pdo->exec("CREATE TABLE IF NOT EXISTS mobile_user_license_events (
            id INT AUTO_INCREMENT PRIMARY KEY,
            user_id INT NOT NULL,
            actor_user_id INT NULL,
            event_type VARCHAR(40) NOT NULL,
            event_label VARCHAR(190) NOT NULL,
            email_snapshot VARCHAR(190) NULL,
            device_id VARCHAR(191) NULL,
            device_fingerprint VARCHAR(191) NULL,
            platform VARCHAR(30) NULL,
            model VARCHAR(120) NULL,
            ip_address VARCHAR(64) NULL,
            meta_json TEXT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            KEY idx_mobile_license_events_user (user_id),
            KEY idx_mobile_license_events_type (event_type),
            KEY idx_mobile_license_events_created (created_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
    }

    public static function ensureUserLicense(PDO $pdo, array $user): array
    {
        self::ensureSchema($pdo);

        $userId = (int)($user['id'] ?? 0);
        $email = self::normalizeEmail($user['email'] ?? null);
        if ($userId <= 0 || $email === null) {
            return [];
        }

        $existing = self::getLicenseByUserId($pdo, $userId);
        if ($existing) {
            if (($existing['email_snapshot'] ?? '') !== $email) {
                $pdo->prepare('UPDATE mobile_user_licenses SET email_snapshot = ? WHERE user_id = ?')->execute([$email, $userId]);
                $existing['email_snapshot'] = $email;
            }
            return $existing;
        }

        $pdo->prepare('INSERT INTO mobile_user_licenses (user_id, email_snapshot, is_enabled, created_at, updated_at) VALUES (?, ?, 0, NOW(), NOW())')
            ->execute([$userId, $email]);

        return self::getLicenseByUserId($pdo, $userId) ?: [];
    }

    public static function generateActivationCode(PDO $pdo, array $user, ?int $actorUserId = null): array
    {
        $license = self::ensureUserLicense($pdo, $user);
        if (!$license) {
            throw new \RuntimeException('license_user_missing');
        }

        $code = self::buildReadableCode();
        $hint = self::maskCode($code);
        $hash = hash('sha256', self::normalizeCode($code));
        $expiresAt = (new \DateTimeImmutable('now'))
            ->add(new \DateInterval('P' . self::ACTIVATION_CODE_TTL_DAYS . 'D'))
            ->format('Y-m-d H:i:s');

        $pdo->prepare('UPDATE mobile_user_licenses
                       SET is_enabled = 1,
                           activation_code_hash = ?,
                           activation_code_hint = ?,
                           activation_code_expires_at = ?,
                           code_generated_at = NOW(),
                           code_generated_by = ?,
                           updated_at = NOW()
                       WHERE user_id = ?')
            ->execute([$hash, $hint, $expiresAt, $actorUserId, (int)$user['id']]);

        self::logEvent($pdo, [
            'user_id' => (int)$user['id'],
            'actor_user_id' => $actorUserId,
            'event_type' => 'code_generated',
            'event_label' => 'Code d\'activation généré',
            'email_snapshot' => self::normalizeEmail($user['email'] ?? null),
            'meta' => [
                'expires_at' => $expiresAt,
                'code_hint' => $hint,
            ],
        ]);

        $license = self::getLicenseByUserId($pdo, (int)$user['id']) ?: [];

        return [
            'code' => $code,
            'expires_at' => $expiresAt,
            'license' => $license,
        ];
    }

    public static function sendActivationCodeEmail(PDO $pdo, array $user, string $code): bool
    {
        $email = self::normalizeEmail($user['email'] ?? null);
        if ($email === null) {
            return false;
        }

        $appName = self::fetchAppName($pdo);
        $subject = 'Code d\'activation mobile - ' . $appName;
        $name = trim((string)($user['name'] ?? 'Utilisateur')) ?: 'Utilisateur';
        $baseUrl = rtrim(base_url(), '/');

        $html = '<div style="font-family:Arial,sans-serif;line-height:1.6;color:#0f172a">'
              . '<p>Bonjour ' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . ',</p>'
              . '<p>Votre accès à l\'application mobile a été préparé.</p>'
              . '<p>Code d\'activation :</p>'
              . '<p style="font-size:24px;font-weight:700;letter-spacing:3px;padding:12px 16px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:12px;display:inline-block;">'
              . htmlspecialchars($code, ENT_QUOTES, 'UTF-8')
              . '</p>'
              . '<p>Utilisez ce code avec votre adresse email sur l\'écran d\'activation de l\'application mobile, avant la première authentification.</p>'
              . '<p>Portail: <a href="' . htmlspecialchars($baseUrl, ENT_QUOTES, 'UTF-8') . '">' . htmlspecialchars($baseUrl, ENT_QUOTES, 'UTF-8') . '</a></p>'
              . '<p style="color:#64748b">Si vous n\'êtes pas concerné, ignorez cet email.</p>'
              . '</div>';

        $sent = Mailer::send($email, $name, $subject, $html, $pdo);
        self::logEvent($pdo, [
            'user_id' => (int)($user['id'] ?? 0),
            'event_type' => $sent ? 'code_emailed' : 'code_email_failed',
            'event_label' => $sent ? 'Code d\'activation envoyé par email' : 'Échec d\'envoi du code d\'activation',
            'email_snapshot' => $email,
        ]);

        return $sent;
    }

    public static function getLicenseByUserId(PDO $pdo, int $userId): ?array
    {
        self::ensureSchema($pdo);
        $stmt = $pdo->prepare('SELECT * FROM mobile_user_licenses WHERE user_id = ? LIMIT 1');
        $stmt->execute([$userId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    }

    public static function getLicenseByEmail(PDO $pdo, string $email): ?array
    {
        self::ensureSchema($pdo);
        $normalized = self::normalizeEmail($email);
        if ($normalized === null) {
            return null;
        }

        $stmt = $pdo->prepare('SELECT l.*, u.name, u.email, u.active FROM mobile_user_licenses l JOIN users u ON u.id = l.user_id WHERE LOWER(l.email_snapshot) = ? LIMIT 1');
        $stmt->execute([$normalized]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    }

    public static function findActivationByDevice(PDO $pdo, string $deviceId = '', string $deviceFingerprint = ''): ?array
    {
        self::ensureSchema($pdo);

        $deviceId = trim($deviceId);
        $deviceFingerprint = trim($deviceFingerprint);
        if ($deviceId === '' && $deviceFingerprint === '') {
            return null;
        }

        if ($deviceId !== '') {
            $stmt = $pdo->prepare('SELECT l.*, u.name, u.email FROM mobile_user_licenses l JOIN users u ON u.id = l.user_id WHERE l.is_enabled = 1 AND l.activated_at IS NOT NULL AND l.activated_device_id = ? LIMIT 1');
            $stmt->execute([$deviceId]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($row) {
                return $row;
            }
        }

        if ($deviceFingerprint !== '') {
            $stmt = $pdo->prepare('SELECT l.*, u.name, u.email FROM mobile_user_licenses l JOIN users u ON u.id = l.user_id WHERE l.is_enabled = 1 AND l.activated_at IS NOT NULL AND l.activated_device_fingerprint = ? LIMIT 1');
            $stmt->execute([$deviceFingerprint]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($row) {
                return $row;
            }
        }

        return null;
    }

    public static function activateLicense(PDO $pdo, array $user, string $code, array $device, ?string $ipAddress = null): array
    {
        $license = self::ensureUserLicense($pdo, $user);
        if (!$license) {
            throw new \RuntimeException('license_missing');
        }

        if ((int)($license['is_enabled'] ?? 0) !== 1) {
            throw new \RuntimeException('license_disabled');
        }

        $expiresAt = trim((string)($license['activation_code_expires_at'] ?? ''));
        if ($expiresAt === '' || strtotime($expiresAt) === false || strtotime($expiresAt) < time()) {
            throw new \RuntimeException('code_expired');
        }

        $expectedHash = trim((string)($license['activation_code_hash'] ?? ''));
        if ($expectedHash === '' || !hash_equals($expectedHash, hash('sha256', self::normalizeCode($code)))) {
            throw new \RuntimeException('code_invalid');
        }

        $activationCount = max(0, (int)($license['activation_count'] ?? 0)) + 1;
        $firstActivatedAt = !empty($license['first_activated_at']) ? $license['first_activated_at'] : date('Y-m-d H:i:s');
        $activatedEmail = self::normalizeEmail($user['email'] ?? null);

        $pdo->prepare('UPDATE mobile_user_licenses
                       SET first_activated_at = ?,
                           activated_at = NOW(),
                           activated_email = ?,
                           activated_device_id = ?,
                           activated_device_fingerprint = ?,
                           activated_platform = ?,
                           activated_model = ?,
                           activation_count = ?,
                           activation_code_hash = NULL,
                           activation_code_hint = NULL,
                           activation_code_expires_at = NULL,
                           updated_at = NOW()
                       WHERE user_id = ?')
            ->execute([
                $firstActivatedAt,
                $activatedEmail,
                trim((string)($device['device_id'] ?? '')) ?: null,
                trim((string)($device['device_fingerprint'] ?? '')) ?: null,
                trim((string)($device['platform'] ?? '')) ?: null,
                trim((string)($device['model'] ?? '')) ?: null,
                $activationCount,
                (int)$user['id'],
            ]);

        self::logEvent($pdo, [
            'user_id' => (int)$user['id'],
            'event_type' => 'activated',
            'event_label' => 'Accès mobile activé',
            'email_snapshot' => $activatedEmail,
            'device_id' => trim((string)($device['device_id'] ?? '')),
            'device_fingerprint' => trim((string)($device['device_fingerprint'] ?? '')),
            'platform' => trim((string)($device['platform'] ?? '')),
            'model' => trim((string)($device['model'] ?? '')),
            'ip_address' => $ipAddress,
            'meta' => ['activation_count' => $activationCount],
        ]);

        return self::getLicenseByUserId($pdo, (int)$user['id']) ?: [];
    }

    public static function updateUserLastLogin(PDO $pdo, int $userId, string $channel = 'web'): void
    {
        if ($userId <= 0) {
            return;
        }

        try {
            $stmt = $pdo->prepare('SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1');
            $stmt->execute(['users', 'last_login']);
            if (!$stmt->fetchColumn()) {
                $pdo->exec('ALTER TABLE users ADD COLUMN last_login TIMESTAMP NULL DEFAULT NULL');
            }

            $pdo->prepare('UPDATE users SET last_login = NOW() WHERE id = ?')->execute([$userId]);

            self::ensureSchema($pdo);
            $pdo->prepare('UPDATE mobile_user_licenses SET last_login_at = NOW() WHERE user_id = ?')->execute([$userId]);
            self::logEvent($pdo, [
                'user_id' => $userId,
                'event_type' => 'login_' . $channel,
                'event_label' => 'Connexion ' . ($channel === 'mobile' ? 'mobile' : 'web'),
            ]);
        } catch (\Throwable $e) {
        }
    }

    public static function setLicenseEnabled(PDO $pdo, int $userId, bool $enabled, ?int $actorUserId = null): void
    {
        self::ensureSchema($pdo);
        $pdo->prepare('UPDATE mobile_user_licenses SET is_enabled = ?, updated_at = NOW() WHERE user_id = ?')->execute([$enabled ? 1 : 0, $userId]);
        self::logEvent($pdo, [
            'user_id' => $userId,
            'actor_user_id' => $actorUserId,
            'event_type' => $enabled ? 'enabled' : 'disabled',
            'event_label' => $enabled ? 'Licence mobile activée' : 'Licence mobile suspendue',
        ]);
    }

    public static function listLicenses(PDO $pdo, ?string $search = null): array
    {
        self::ensureSchema($pdo);
        Notifier::ensureDevicesTable($pdo);
        $search = trim((string)$search);
        $sql = 'SELECT u.id AS user_id,
                       u.name,
                       u.email,
                       u.role_key,
                       u.active,
                       u.last_login,
                       l.is_enabled,
                       l.activation_code_hint,
                       l.activation_code_expires_at,
                       l.code_generated_at,
                       l.first_activated_at,
                       l.activated_at,
                       l.activated_email,
                       l.activated_device_id,
                       l.activated_platform,
                       l.activated_model,
                       l.activation_count,
                       l.last_login_at AS mobile_last_login_at,
                       ud.last_seen_at AS device_last_seen_at
                FROM users u
                LEFT JOIN mobile_user_licenses l ON l.user_id = u.id
                LEFT JOIN (
                    SELECT user_id, MAX(last_seen_at) AS last_seen_at
                    FROM user_devices
                    GROUP BY user_id
                ) ud ON ud.user_id = u.id
                WHERE u.active = 1';

        $params = [];
        if ($search !== '') {
            $sql .= ' AND (
                        u.name LIKE ?
                        OR u.email LIKE ?
                        OR u.role_key LIKE ?
                        OR l.activation_code_hint LIKE ?
                        OR l.activated_email LIKE ?
                      )';
            $term = '%' . $search . '%';
            $params = [$term, $term, $term, $term, $term];
        }

        $sql .= '
                ORDER BY u.name ASC';

        if ($params !== []) {
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            return $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        }

        return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public static function listRecentEvents(PDO $pdo, int $limit = 80, ?string $search = null): array
    {
        self::ensureSchema($pdo);
        $limit = max(1, min(200, $limit));
        $search = trim((string)$search);
        $sql = 'SELECT e.*, u.name AS user_name, u.email AS user_email, a.name AS actor_name
                FROM mobile_user_license_events e
                LEFT JOIN users u ON u.id = e.user_id
                LEFT JOIN users a ON a.id = e.actor_user_id';

        $params = [];
        if ($search !== '') {
            $sql .= ' WHERE (
                        u.name LIKE ?
                        OR u.email LIKE ?
                        OR e.email_snapshot LIKE ?
                        OR e.event_label LIKE ?
                        OR e.event_type LIKE ?
                        OR e.platform LIKE ?
                        OR e.model LIKE ?
                        OR a.name LIKE ?
                      )';
            $term = '%' . $search . '%';
            $params = [$term, $term, $term, $term, $term, $term, $term, $term];
        }

        $sql .= '
                ORDER BY e.created_at DESC
                LIMIT ' . (int)$limit;

        if ($params !== []) {
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
            return $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        }

        return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public static function normalizeEmail($value): ?string
    {
        $email = strtolower(trim((string)$value));
        return filter_var($email, FILTER_VALIDATE_EMAIL) ? $email : null;
    }

    public static function normalizeCode(string $code): string
    {
        return strtoupper(preg_replace('/[^A-Z0-9]/i', '', $code));
    }

    private static function buildReadableCode(): string
    {
        $alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
        $bytes = random_bytes(8);
        $parts = [];
        for ($chunk = 0; $chunk < 2; $chunk++) {
            $segment = '';
            for ($index = 0; $index < 4; $index++) {
                $segment .= $alphabet[ord($bytes[$chunk * 4 + $index]) % strlen($alphabet)];
            }
            $parts[] = $segment;
        }
        return implode('-', $parts);
    }

    private static function maskCode(string $code): string
    {
        $normalized = self::normalizeCode($code);
        $visible = substr($normalized, -4);
        return '****-' . $visible;
    }

    private static function logEvent(PDO $pdo, array $payload): void
    {
        self::ensureSchema($pdo);
        $stmt = $pdo->prepare('INSERT INTO mobile_user_license_events (user_id, actor_user_id, event_type, event_label, email_snapshot, device_id, device_fingerprint, platform, model, ip_address, meta_json, created_at)
                               VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())');
        $metaJson = null;
        if (array_key_exists('meta', $payload)) {
            $metaJson = json_encode($payload['meta'], JSON_UNESCAPED_UNICODE);
        }
        $stmt->execute([
            (int)($payload['user_id'] ?? 0),
            isset($payload['actor_user_id']) ? (int)$payload['actor_user_id'] : null,
            (string)($payload['event_type'] ?? 'event'),
            (string)($payload['event_label'] ?? 'Événement mobile'),
            self::normalizeEmail($payload['email_snapshot'] ?? null),
            trim((string)($payload['device_id'] ?? '')) ?: null,
            trim((string)($payload['device_fingerprint'] ?? '')) ?: null,
            trim((string)($payload['platform'] ?? '')) ?: null,
            trim((string)($payload['model'] ?? '')) ?: null,
            trim((string)($payload['ip_address'] ?? '')) ?: null,
            $metaJson,
        ]);
    }

    private static function fetchAppName(PDO $pdo): string
    {
        try {
            $stmt = $pdo->prepare('SELECT value FROM settings WHERE `key` = ? LIMIT 1');
            $stmt->execute(['app_name']);
            $appName = trim((string)$stmt->fetchColumn());
            if ($appName !== '') {
                return $appName;
            }
        } catch (\Throwable $e) {
        }

        return 'Insuite Technicien';
    }
}