<?php

declare(strict_types=1);

namespace App\Services;

use ClientDetector;
use Session;

/**
 * EventLogService - Système de logs d'événements JSONL
 *
 * Enregistre tous les événements métier (authentification, CRUD passages/secteurs/users/entités/opérations)
 * dans des fichiers JSONL quotidiens pour statistiques et audit.
 *
 * Format : Un événement = une ligne JSON
 * Stockage : /logs/events/YYYY-MM-DD.jsonl
 * Rétention : 15 mois (compression après 30 jours)
 *
 * @see docs/EVENTS-LOG.md
 */
class EventLogService
{
    /** @var string Chemin du dossier de logs d'événements */
    private const LOG_DIR = __DIR__ . '/../../logs/events';

    /** @var int Permissions du dossier */
    private const DIR_PERMISSIONS = 0750;

    /** @var int Permissions des fichiers */
    private const FILE_PERMISSIONS = 0640;

    // ==================== MÉTHODES D'AUTHENTIFICATION ====================

    /**
     * Log une connexion réussie
     *
     * @param int $userId ID utilisateur (users.id)
     * @param int|null $entityId ID entité
     * @param string $username Nom d'utilisateur
     */
    public static function logLoginSuccess(int $userId, ?int $entityId, string $username): void
    {
        self::writeEvent('login_success', [
            'user_id' => $userId,
            'entity_id' => $entityId,
            'username' => $username
        ]);
    }

    /**
     * Log une tentative de connexion échouée
     *
     * @param string $username Nom d'utilisateur tenté
     * @param string $reason Raison (invalid_password, user_not_found, account_inactive, blocked_ip)
     * @param int $attempt Numéro de tentative
     */
    public static function logLoginFailed(string $username, string $reason, int $attempt = 1): void
    {
        self::writeEvent('login_failed', [
            'username' => $username,
            'reason' => $reason,
            'attempt' => $attempt
        ]);
    }

    /**
     * Log une déconnexion
     *
     * @param int $userId ID utilisateur
     * @param int|null $entityId ID entité
     * @param int $sessionDuration Durée session en secondes
     */
    public static function logLogout(int $userId, ?int $entityId, int $sessionDuration = 0): void
    {
        self::writeEvent('logout', [
            'user_id' => $userId,
            'entity_id' => $entityId,
            'session_duration' => $sessionDuration
        ]);
    }

    // ==================== MÉTHODES PASSAGES ====================

    /**
     * Log la création d'un passage
     *
     * @param int $passageId ID du passage créé
     * @param int $operationId ID opération
     * @param int $sectorId ID secteur
     * @param float $amount Montant
     * @param string $paymentType Type paiement (cash, stripe, check, etc.)
     */
    public static function logPassageCreated(
        int $passageId,
        int $operationId,
        int $sectorId,
        float $amount,
        string $paymentType
    ): void {
        self::writeEvent('passage_created', [
            'passage_id' => $passageId,
            'operation_id' => $operationId,
            'sector_id' => $sectorId,
            'amount' => $amount,
            'payment_type' => $paymentType
        ]);
    }

    /**
     * Log la modification d'un passage
     *
     * @param int $passageId ID du passage
     * @param array $changes Tableau des changements ['field' => ['old' => val, 'new' => val]]
     */
    public static function logPassageUpdated(int $passageId, array $changes): void
    {
        self::writeEvent('passage_updated', [
            'passage_id' => $passageId,
            'changes' => $changes
        ]);
    }

    /**
     * Log la suppression d'un passage
     *
     * @param int $passageId ID du passage
     * @param int $operationId ID opération
     * @param bool $softDelete Suppression logique ou physique
     */
    public static function logPassageDeleted(int $passageId, int $operationId, bool $softDelete = true): void
    {
        $userId = Session::getUserId();
        self::writeEvent('passage_deleted', [
            'passage_id' => $passageId,
            'operation_id' => $operationId,
            'deleted_by' => $userId,
            'soft_delete' => $softDelete
        ]);
    }

    // ==================== MÉTHODES SECTEURS ====================

    /**
     * Log la création d'un secteur
     *
     * @param int $sectorId ID du secteur créé
     * @param int $operationId ID opération
     * @param string $sectorName Nom du secteur
     */
    public static function logSectorCreated(int $sectorId, int $operationId, string $sectorName): void
    {
        self::writeEvent('sector_created', [
            'sector_id' => $sectorId,
            'operation_id' => $operationId,
            'sector_name' => $sectorName
        ]);
    }

    /**
     * Log la modification d'un secteur
     *
     * @param int $sectorId ID du secteur
     * @param int $operationId ID opération
     * @param array $changes Tableau des changements
     */
    public static function logSectorUpdated(int $sectorId, int $operationId, array $changes): void
    {
        self::writeEvent('sector_updated', [
            'sector_id' => $sectorId,
            'operation_id' => $operationId,
            'changes' => $changes
        ]);
    }

    /**
     * Log la suppression d'un secteur
     *
     * @param int $sectorId ID du secteur
     * @param int $operationId ID opération
     * @param bool $softDelete Suppression logique ou physique
     */
    public static function logSectorDeleted(int $sectorId, int $operationId, bool $softDelete = true): void
    {
        $userId = Session::getUserId();
        self::writeEvent('sector_deleted', [
            'sector_id' => $sectorId,
            'operation_id' => $operationId,
            'deleted_by' => $userId,
            'soft_delete' => $softDelete
        ]);
    }

    // ==================== MÉTHODES USERS ====================

    /**
     * Log la création d'un utilisateur
     *
     * @param int $newUserId ID utilisateur créé
     * @param int $entityId ID entité
     * @param int $roleId ID rôle
     * @param string $username Nom d'utilisateur
     */
    public static function logUserCreated(int $newUserId, int $entityId, int $roleId, string $username): void
    {
        $createdBy = Session::getUserId();
        self::writeEvent('user_created', [
            'new_user_id' => $newUserId,
            'entity_id' => $entityId,
            'created_by' => $createdBy,
            'role_id' => $roleId,
            'username' => $username
        ]);
    }

    /**
     * Log la modification d'un utilisateur
     *
     * @param int $userId ID utilisateur modifié
     * @param array $changes Tableau des changements (booléen pour champs chiffrés)
     */
    public static function logUserUpdated(int $userId, array $changes): void
    {
        $updatedBy = Session::getUserId();
        $entityId = Session::getEntityId();

        self::writeEvent('user_updated', [
            'user_id' => $userId,
            'entity_id' => $entityId,
            'updated_by' => $updatedBy,
            'changes' => $changes
        ]);
    }

    /**
     * Log la suppression d'un utilisateur
     *
     * @param int $userId ID utilisateur supprimé
     * @param bool $softDelete Suppression logique ou physique
     */
    public static function logUserDeleted(int $userId, bool $softDelete = true): void
    {
        $deletedBy = Session::getUserId();
        $entityId = Session::getEntityId();

        self::writeEvent('user_deleted', [
            'user_id' => $userId,
            'entity_id' => $entityId,
            'deleted_by' => $deletedBy,
            'soft_delete' => $softDelete
        ]);
    }

    // ==================== MÉTHODES ENTITÉS ====================

    /**
     * Log la création d'une entité
     *
     * @param int $entityId ID entité créée
     * @param int $entityTypeId Type d'entité
     * @param string $postalCode Code postal
     */
    public static function logEntityCreated(int $entityId, int $entityTypeId, string $postalCode): void
    {
        $createdBy = Session::getUserId() ?? 1; // Super-admin par défaut
        self::writeEvent('entity_created', [
            'entity_id' => $entityId,
            'created_by' => $createdBy,
            'entity_type_id' => $entityTypeId,
            'postal_code' => $postalCode
        ]);
    }

    /**
     * Log la modification d'une entité
     *
     * @param int $entityId ID entité
     * @param array $changes Tableau des changements (booléen pour champs chiffrés)
     */
    public static function logEntityUpdated(int $entityId, array $changes): void
    {
        $updatedBy = Session::getUserId();
        self::writeEvent('entity_updated', [
            'entity_id' => $entityId,
            'user_id' => $updatedBy,
            'updated_by' => $updatedBy,
            'changes' => $changes
        ]);
    }

    /**
     * Log la suppression d'une entité
     *
     * @param int $entityId ID entité
     * @param string $reason Raison de la suppression
     */
    public static function logEntityDeleted(int $entityId, string $reason = ''): void
    {
        $deletedBy = Session::getUserId() ?? 1;
        self::writeEvent('entity_deleted', [
            'entity_id' => $entityId,
            'deleted_by' => $deletedBy,
            'soft_delete' => true,
            'reason' => $reason
        ]);
    }

    // ==================== MÉTHODES STRIPE ====================

    /**
     * Log la création d'un PaymentIntent
     *
     * @param string $paymentIntentId ID Stripe du PaymentIntent
     * @param int $passageId ID du passage
     * @param int $amount Montant en centimes
     * @param string $method Méthode (tap_to_pay, qr_code, web)
     */
    public static function logStripePaymentCreated(
        string $paymentIntentId,
        int $passageId,
        int $amount,
        string $method
    ): void {
        $entityId = Session::getEntityId();
        self::writeEvent('stripe_payment_created', [
            'payment_intent_id' => $paymentIntentId,
            'passage_id' => $passageId,
            'entity_id' => $entityId,
            'amount' => $amount,
            'method' => $method
        ]);
    }

    /**
     * Log un paiement Stripe réussi
     *
     * @param string $paymentIntentId ID Stripe du PaymentIntent
     * @param int $passageId ID du passage
     * @param int $amount Montant en centimes
     * @param string $method Méthode (tap_to_pay, qr_code, web)
     */
    public static function logStripePaymentSuccess(
        string $paymentIntentId,
        int $passageId,
        int $amount,
        string $method
    ): void {
        $entityId = Session::getEntityId();
        self::writeEvent('stripe_payment_success', [
            'payment_intent_id' => $paymentIntentId,
            'passage_id' => $passageId,
            'entity_id' => $entityId,
            'amount' => $amount,
            'method' => $method
        ]);
    }

    /**
     * Log un paiement Stripe échoué
     *
     * @param string|null $paymentIntentId ID Stripe (peut être null si création échouée)
     * @param int|null $passageId ID du passage (peut être null)
     * @param int|null $amount Montant en centimes (peut être null)
     * @param string $method Méthode tentée
     * @param string $errorCode Code d'erreur
     * @param string $errorMessage Message d'erreur
     */
    public static function logStripePaymentFailed(
        ?string $paymentIntentId,
        ?int $passageId,
        ?int $amount,
        string $method,
        string $errorCode,
        string $errorMessage
    ): void {
        $entityId = Session::getEntityId();
        $data = [
            'entity_id' => $entityId,
            'method' => $method,
            'error_code' => $errorCode,
            'error_message' => $errorMessage
        ];

        if ($paymentIntentId !== null) {
            $data['payment_intent_id'] = $paymentIntentId;
        }
        if ($passageId !== null) {
            $data['passage_id'] = $passageId;
        }
        if ($amount !== null) {
            $data['amount'] = $amount;
        }

        self::writeEvent('stripe_payment_failed', $data);
    }

    /**
     * Log l'annulation d'un paiement Stripe
     *
     * @param string $paymentIntentId ID Stripe du PaymentIntent
     * @param int|null $passageId ID du passage (peut être null)
     * @param string $reason Raison (user_cancelled, timeout, error, etc.)
     */
    public static function logStripePaymentCancelled(
        string $paymentIntentId,
        ?int $passageId,
        string $reason
    ): void {
        $entityId = Session::getEntityId();
        $data = [
            'payment_intent_id' => $paymentIntentId,
            'entity_id' => $entityId,
            'reason' => $reason
        ];

        if ($passageId !== null) {
            $data['passage_id'] = $passageId;
        }

        self::writeEvent('stripe_payment_cancelled', $data);
    }

    /**
     * Log une erreur du Terminal Tap to Pay
     *
     * @param string $errorCode Code d'erreur (cardReadTimedOut, device_not_compatible, etc.)
     * @param string $errorMessage Message d'erreur
     * @param array $metadata Métadonnées supplémentaires (device_model, is_simulated, etc.)
     */
    public static function logStripeTerminalError(
        string $errorCode,
        string $errorMessage,
        array $metadata = []
    ): void {
        $entityId = Session::getEntityId();
        self::writeEvent('stripe_terminal_error', array_merge([
            'entity_id' => $entityId,
            'error_code' => $errorCode,
            'error_message' => $errorMessage
        ], $metadata));
    }

    // ==================== MÉTHODES OPÉRATIONS ====================

    /**
     * Log la création d'une opération
     *
     * @param int $operationId ID opération créée
     * @param string $dateStart Date début (YYYY-MM-DD)
     * @param string $dateEnd Date fin (YYYY-MM-DD)
     */
    public static function logOperationCreated(int $operationId, string $dateStart, string $dateEnd): void
    {
        $entityId = Session::getEntityId();
        $createdBy = Session::getUserId();

        self::writeEvent('operation_created', [
            'operation_id' => $operationId,
            'entity_id' => $entityId,
            'created_by' => $createdBy,
            'date_start' => $dateStart,
            'date_end' => $dateEnd
        ]);
    }

    /**
     * Log la modification d'une opération
     *
     * @param int $operationId ID opération
     * @param array $changes Tableau des changements
     */
    public static function logOperationUpdated(int $operationId, array $changes): void
    {
        $entityId = Session::getEntityId();
        $updatedBy = Session::getUserId();

        self::writeEvent('operation_updated', [
            'operation_id' => $operationId,
            'entity_id' => $entityId,
            'updated_by' => $updatedBy,
            'changes' => $changes
        ]);
    }

    /**
     * Log la suppression d'une opération
     *
     * @param int $operationId ID opération
     * @param bool $softDelete Suppression logique ou physique
     */
    public static function logOperationDeleted(int $operationId, bool $softDelete = true): void
    {
        $entityId = Session::getEntityId();
        $deletedBy = Session::getUserId();

        self::writeEvent('operation_deleted', [
            'operation_id' => $operationId,
            'entity_id' => $entityId,
            'deleted_by' => $deletedBy,
            'soft_delete' => $softDelete
        ]);
    }

    // ==================== MÉTHODES PRIVÉES ====================

    /**
     * Méthode centrale d'écriture d'un événement
     *
     * @param string $eventName Nom de l'événement
     * @param array $data Données spécifiques à l'événement
     */
    private static function writeEvent(string $eventName, array $data): void
    {
        try {
            // Enrichir avec timestamp, user_id, entity_id, IP, platform, app_version
            $event = self::enrichEvent($eventName, $data);

            // Générer le chemin du fichier quotidien
            $filename = self::LOG_DIR . '/' . date('Y-m-d') . '.jsonl';

            // Créer le dossier si nécessaire
            self::ensureLogDirectoryExists();

            // Encoder en JSON compact (une ligne)
            $jsonLine = json_encode($event, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";

            // Écrire en mode append
            if (file_put_contents($filename, $jsonLine, FILE_APPEND | LOCK_EX) === false) {
                error_log("EventLogService: Impossible d'écrire dans {$filename}");
                return;
            }

            // Appliquer les permissions au fichier
            if (file_exists($filename)) {
                @chmod($filename, self::FILE_PERMISSIONS);
            }
        } catch (\Throwable $e) {
            // Ne jamais bloquer l'application si le logging échoue
            error_log("EventLogService: Erreur lors de l'écriture de l'événement {$eventName}: " . $e->getMessage());
        }
    }

    /**
     * Enrichit un événement avec les métadonnées communes
     *
     * @param string $eventName Nom de l'événement
     * @param array $data Données de l'événement
     * @return array Événement enrichi
     */
    private static function enrichEvent(string $eventName, array $data): array
    {
        // Récupérer les informations client
        $clientInfo = ClientDetector::getClientInfo();

        // Structure de base
        $event = [
            'timestamp' => gmdate('Y-m-d\TH:i:s\Z'), // ISO 8601 UTC
            'event' => $eventName,
        ];

        // Ajouter user_id si disponible et pas déjà dans $data
        if (!isset($data['user_id'])) {
            $userId = Session::getUserId();
            if ($userId !== null) {
                $event['user_id'] = $userId;
            }
        }

        // Ajouter entity_id si disponible et pas déjà dans $data
        if (!isset($data['entity_id'])) {
            $entityId = Session::getEntityId();
            if ($entityId !== null) {
                $event['entity_id'] = $entityId;
            }
        }

        // Fusionner avec les données spécifiques
        $event = array_merge($event, $data);

        // Ajouter IP
        $event['ip'] = $clientInfo['ip'];

        // Ajouter platform
        $event['platform'] = self::getPlatform($clientInfo);

        // Ajouter app_version si mobile
        if ($event['platform'] === 'ios' || $event['platform'] === 'android') {
            $appVersion = self::getAppVersion($clientInfo);
            if ($appVersion !== null) {
                $event['app_version'] = $appVersion;
            }
        }

        return $event;
    }

    /**
     * Détermine la plateforme (ios, android, web)
     *
     * @param array $clientInfo Informations client de ClientDetector
     * @return string Platform (ios|android|web)
     */
    private static function getPlatform(array $clientInfo): string
    {
        if ($clientInfo['type'] !== 'mobile') {
            return 'web';
        }

        $userAgent = $clientInfo['userAgent'];

        // Détection iOS
        if (stripos($userAgent, 'iOS') !== false ||
            stripos($userAgent, 'iPhone') !== false ||
            stripos($userAgent, 'iPad') !== false) {
            return 'ios';
        }

        // Détection Android
        if (stripos($userAgent, 'Android') !== false) {
            return 'android';
        }

        // Par défaut mobile générique = web
        return 'web';
    }

    /**
     * Extrait la version de l'application depuis le User-Agent
     * Format attendu: AppName/VersionNumber ou Platform/Version AppName/Version
     *
     * @param array $clientInfo Informations client
     * @return string|null Version de l'app ou null
     */
    private static function getAppVersion(array $clientInfo): ?string
    {
        $userAgent = $clientInfo['userAgent'];

        // Tentative extraction format: GeoSector/3.3.6
        if (preg_match('/GeoSector\/([0-9\.]+)/', $userAgent, $matches)) {
            return $matches[1];
        }

        // Format alternatif: AppName/Version
        if (preg_match('/([A-Za-z0-9_]+)\/([0-9\.]+)/', $userAgent, $matches)) {
            return $matches[2];
        }

        return null;
    }

    /**
     * Crée le dossier de logs si nécessaire avec les bonnes permissions
     */
    private static function ensureLogDirectoryExists(): void
    {
        if (!is_dir(self::LOG_DIR)) {
            if (!@mkdir(self::LOG_DIR, self::DIR_PERMISSIONS, true)) {
                error_log("EventLogService: Impossible de créer le dossier " . self::LOG_DIR);
                return;
            }
        }

        // Vérifier les permissions
        if (!is_writable(self::LOG_DIR)) {
            @chmod(self::LOG_DIR, self::DIR_PERMISSIONS);
        }
    }
}
