<?php

declare(strict_types=1);

namespace App\Services;

use Database;
use PDO;

/**
 * EventStatsService - Service de statistiques d'événements
 *
 * Fournit des méthodes pour récupérer les statistiques agrégées
 * depuis la table event_stats_daily et le détail depuis les fichiers JSONL.
 *
 * @see docs/TECHBOOK.md section "Statistiques Events"
 */
class EventStatsService
{
    /** @var string Chemin du dossier des logs événements */
    private const EVENT_LOG_DIR = __DIR__ . '/../../logs/events';

    /** @var int Limite max pour le détail */
    private const MAX_DETAILS_LIMIT = 100;

    /** @var PDO Instance de la base de données */
    private PDO $db;

    public function __construct()
    {
        $this->db = Database::getInstance();
    }

    // ==================== MÉTHODES PUBLIQUES ====================

    /**
     * Récupère le résumé des stats pour une date donnée
     *
     * @param int|null $entityId ID entité (null = toutes entités pour super-admin)
     * @param string|null $date Date (YYYY-MM-DD), défaut = aujourd'hui
     * @return array Stats résumées par catégorie
     */
    public function getSummary(?int $entityId, ?string $date = null): array
    {
        $date = $date ?? date('Y-m-d');

        $sql = "
            SELECT event_type, count, sum_amount, unique_users, metadata
            FROM event_stats_daily
            WHERE stat_date = :date
        ";
        $params = ['date' => $date];

        if ($entityId !== null) {
            $sql .= " AND entity_id = :entity_id";
            $params['entity_id'] = $entityId;
        } else {
            $sql .= " AND entity_id IS NULL";
        }

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

        return $this->formatSummary($date, $rows);
    }

    /**
     * Récupère les stats journalières sur une plage de dates
     *
     * @param int|null $entityId ID entité (null = toutes entités)
     * @param string $from Date début (YYYY-MM-DD)
     * @param string $to Date fin (YYYY-MM-DD)
     * @param array $eventTypes Filtrer par types d'événements (optionnel)
     * @return array Stats par jour
     */
    public function getDaily(?int $entityId, string $from, string $to, array $eventTypes = []): array
    {
        $sql = "
            SELECT stat_date, event_type, count, sum_amount, unique_users
            FROM event_stats_daily
            WHERE stat_date BETWEEN :from AND :to
        ";
        $params = ['from' => $from, 'to' => $to];

        if ($entityId !== null) {
            $sql .= " AND entity_id = :entity_id";
            $params['entity_id'] = $entityId;
        } else {
            $sql .= " AND entity_id IS NULL";
        }

        if (!empty($eventTypes)) {
            $placeholders = [];
            foreach ($eventTypes as $i => $type) {
                $key = "event_type_{$i}";
                $placeholders[] = ":{$key}";
                $params[$key] = $type;
            }
            $sql .= " AND event_type IN (" . implode(', ', $placeholders) . ")";
        }

        $sql .= " ORDER BY stat_date ASC, event_type ASC";

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

        return $this->formatDaily($rows);
    }

    /**
     * Récupère les stats hebdomadaires (calculées depuis daily)
     *
     * @param int|null $entityId ID entité
     * @param string $from Date début
     * @param string $to Date fin
     * @param array $eventTypes Filtrer par types d'événements
     * @return array Stats par semaine
     */
    public function getWeekly(?int $entityId, string $from, string $to, array $eventTypes = []): array
    {
        $daily = $this->getDaily($entityId, $from, $to, $eventTypes);

        $weekly = [];

        foreach ($daily as $day) {
            $date = new \DateTime($day['date']);
            $weekStart = clone $date;
            $weekStart->modify('monday this week');
            $weekKey = $weekStart->format('Y-m-d');

            if (!isset($weekly[$weekKey])) {
                $weekly[$weekKey] = [
                    'week_start' => $weekKey,
                    'week_number' => (int) $date->format('W'),
                    'year' => (int) $date->format('Y'),
                    'events' => [],
                    'totals' => [
                        'count' => 0,
                        'sum_amount' => 0.0,
                    ],
                ];
            }

            // Agréger les événements
            foreach ($day['events'] as $eventType => $stats) {
                if (!isset($weekly[$weekKey]['events'][$eventType])) {
                    $weekly[$weekKey]['events'][$eventType] = [
                        'count' => 0,
                        'sum_amount' => 0.0,
                        'unique_users' => 0,
                    ];
                }
                $weekly[$weekKey]['events'][$eventType]['count'] += $stats['count'];
                $weekly[$weekKey]['events'][$eventType]['sum_amount'] += $stats['sum_amount'];
                $weekly[$weekKey]['events'][$eventType]['unique_users'] += $stats['unique_users'];
            }

            $weekly[$weekKey]['totals']['count'] += $day['totals']['count'];
            $weekly[$weekKey]['totals']['sum_amount'] += $day['totals']['sum_amount'];
        }

        return array_values($weekly);
    }

    /**
     * Récupère les stats mensuelles (calculées depuis daily)
     *
     * @param int|null $entityId ID entité
     * @param int $year Année
     * @param array $eventTypes Filtrer par types d'événements
     * @return array Stats par mois
     */
    public function getMonthly(?int $entityId, int $year, array $eventTypes = []): array
    {
        $from = "{$year}-01-01";
        $to = "{$year}-12-31";

        $daily = $this->getDaily($entityId, $from, $to, $eventTypes);

        $monthly = [];

        foreach ($daily as $day) {
            $monthKey = substr($day['date'], 0, 7); // YYYY-MM

            if (!isset($monthly[$monthKey])) {
                $monthly[$monthKey] = [
                    'month' => $monthKey,
                    'year' => $year,
                    'month_number' => (int) substr($monthKey, 5, 2),
                    'events' => [],
                    'totals' => [
                        'count' => 0,
                        'sum_amount' => 0.0,
                    ],
                ];
            }

            // Agréger les événements
            foreach ($day['events'] as $eventType => $stats) {
                if (!isset($monthly[$monthKey]['events'][$eventType])) {
                    $monthly[$monthKey]['events'][$eventType] = [
                        'count' => 0,
                        'sum_amount' => 0.0,
                        'unique_users' => 0,
                    ];
                }
                $monthly[$monthKey]['events'][$eventType]['count'] += $stats['count'];
                $monthly[$monthKey]['events'][$eventType]['sum_amount'] += $stats['sum_amount'];
                $monthly[$monthKey]['events'][$eventType]['unique_users'] += $stats['unique_users'];
            }

            $monthly[$monthKey]['totals']['count'] += $day['totals']['count'];
            $monthly[$monthKey]['totals']['sum_amount'] += $day['totals']['sum_amount'];
        }

        return array_values($monthly);
    }

    /**
     * Récupère le détail des événements depuis le fichier JSONL
     *
     * @param int|null $entityId ID entité (null = tous)
     * @param string $date Date (YYYY-MM-DD)
     * @param string|null $eventType Filtrer par type d'événement
     * @param int $limit Nombre max de résultats
     * @param int $offset Décalage pour pagination
     * @return array Événements détaillés avec pagination
     */
    public function getDetails(
        ?int $entityId,
        string $date,
        ?string $eventType = null,
        int $limit = 50,
        int $offset = 0
    ): array {
        $limit = min($limit, self::MAX_DETAILS_LIMIT);

        $filePath = self::EVENT_LOG_DIR . '/' . $date . '.jsonl';

        if (!file_exists($filePath)) {
            return [
                'date' => $date,
                'events' => [],
                'pagination' => [
                    'total' => 0,
                    'limit' => $limit,
                    'offset' => $offset,
                    'has_more' => false,
                ],
            ];
        }

        $events = [];
        $total = 0;
        $currentIndex = 0;

        $handle = fopen($filePath, 'r');
        if (!$handle) {
            return [
                'date' => $date,
                'events' => [],
                'pagination' => [
                    'total' => 0,
                    'limit' => $limit,
                    'offset' => $offset,
                    'has_more' => false,
                ],
                'error' => 'Impossible de lire le fichier',
            ];
        }

        while (($line = fgets($handle)) !== false) {
            $line = trim($line);
            if (empty($line)) {
                continue;
            }

            $event = json_decode($line, true);
            if (!$event || !isset($event['event'])) {
                continue;
            }

            // Filtrer par entity_id
            if ($entityId !== null) {
                $eventEntityId = $event['entity_id'] ?? null;
                if ($eventEntityId !== $entityId) {
                    continue;
                }
            }

            // Filtrer par event_type
            if ($eventType !== null && ($event['event'] ?? '') !== $eventType) {
                continue;
            }

            $total++;

            // Pagination
            if ($currentIndex >= $offset && count($events) < $limit) {
                $events[] = $this->sanitizeEventForOutput($event);
            }

            $currentIndex++;
        }

        fclose($handle);

        return [
            'date' => $date,
            'events' => $events,
            'pagination' => [
                'total' => $total,
                'limit' => $limit,
                'offset' => $offset,
                'has_more' => ($offset + $limit) < $total,
            ],
        ];
    }

    /**
     * Récupère les types d'événements disponibles
     *
     * @return array Liste des types d'événements
     */
    public function getEventTypes(): array
    {
        return [
            'auth' => ['login_success', 'login_failed', 'logout'],
            'passages' => ['passage_created', 'passage_updated', 'passage_deleted'],
            'sectors' => ['sector_created', 'sector_updated', 'sector_deleted'],
            'users' => ['user_created', 'user_updated', 'user_deleted'],
            'entities' => ['entity_created', 'entity_updated', 'entity_deleted'],
            'operations' => ['operation_created', 'operation_updated', 'operation_deleted'],
            'stripe' => [
                'stripe_payment_created',
                'stripe_payment_success',
                'stripe_payment_failed',
                'stripe_payment_cancelled',
                'stripe_terminal_error',
            ],
        ];
    }

    /**
     * Vérifie si des stats existent pour une date
     *
     * @param string $date Date à vérifier
     * @return bool
     */
    public function hasStatsForDate(string $date): bool
    {
        $stmt = $this->db->prepare("
            SELECT COUNT(*) FROM event_stats_daily WHERE stat_date = :date
        ");
        $stmt->execute(['date' => $date]);
        return (int) $stmt->fetchColumn() > 0;
    }

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

    /**
     * Formate le résumé des stats par catégorie
     */
    private function formatSummary(string $date, array $rows): array
    {
        $summary = [
            'date' => $date,
            'stats' => [
                'auth' => ['success' => 0, 'failed' => 0, 'logout' => 0],
                'passages' => ['created' => 0, 'updated' => 0, 'deleted' => 0, 'amount' => 0.0],
                'users' => ['created' => 0, 'updated' => 0, 'deleted' => 0],
                'sectors' => ['created' => 0, 'updated' => 0, 'deleted' => 0],
                'entities' => ['created' => 0, 'updated' => 0, 'deleted' => 0],
                'operations' => ['created' => 0, 'updated' => 0, 'deleted' => 0],
                'stripe' => ['created' => 0, 'success' => 0, 'failed' => 0, 'cancelled' => 0, 'amount' => 0.0],
            ],
            'totals' => [
                'events' => 0,
                'unique_users' => 0,
            ],
        ];

        $uniqueUsersSet = [];

        foreach ($rows as $row) {
            $eventType = $row['event_type'];
            $count = (int) $row['count'];
            $amount = (float) $row['sum_amount'];
            $uniqueUsers = (int) $row['unique_users'];

            $summary['totals']['events'] += $count;
            $uniqueUsersSet[$eventType] = $uniqueUsers;

            // Mapper vers les catégories
            switch ($eventType) {
                case 'login_success':
                    $summary['stats']['auth']['success'] = $count;
                    break;
                case 'login_failed':
                    $summary['stats']['auth']['failed'] = $count;
                    break;
                case 'logout':
                    $summary['stats']['auth']['logout'] = $count;
                    break;
                case 'passage_created':
                    $summary['stats']['passages']['created'] = $count;
                    $summary['stats']['passages']['amount'] += $amount;
                    break;
                case 'passage_updated':
                    $summary['stats']['passages']['updated'] = $count;
                    break;
                case 'passage_deleted':
                    $summary['stats']['passages']['deleted'] = $count;
                    break;
                case 'user_created':
                    $summary['stats']['users']['created'] = $count;
                    break;
                case 'user_updated':
                    $summary['stats']['users']['updated'] = $count;
                    break;
                case 'user_deleted':
                    $summary['stats']['users']['deleted'] = $count;
                    break;
                case 'sector_created':
                    $summary['stats']['sectors']['created'] = $count;
                    break;
                case 'sector_updated':
                    $summary['stats']['sectors']['updated'] = $count;
                    break;
                case 'sector_deleted':
                    $summary['stats']['sectors']['deleted'] = $count;
                    break;
                case 'entity_created':
                    $summary['stats']['entities']['created'] = $count;
                    break;
                case 'entity_updated':
                    $summary['stats']['entities']['updated'] = $count;
                    break;
                case 'entity_deleted':
                    $summary['stats']['entities']['deleted'] = $count;
                    break;
                case 'operation_created':
                    $summary['stats']['operations']['created'] = $count;
                    break;
                case 'operation_updated':
                    $summary['stats']['operations']['updated'] = $count;
                    break;
                case 'operation_deleted':
                    $summary['stats']['operations']['deleted'] = $count;
                    break;
                case 'stripe_payment_created':
                    $summary['stats']['stripe']['created'] = $count;
                    break;
                case 'stripe_payment_success':
                    $summary['stats']['stripe']['success'] = $count;
                    $summary['stats']['stripe']['amount'] += $amount;
                    break;
                case 'stripe_payment_failed':
                    $summary['stats']['stripe']['failed'] = $count;
                    break;
                case 'stripe_payment_cancelled':
                    $summary['stats']['stripe']['cancelled'] = $count;
                    break;
            }
        }

        // Estimation des utilisateurs uniques (max des catégories car overlap possible)
        $summary['totals']['unique_users'] = !empty($uniqueUsersSet) ? max($uniqueUsersSet) : 0;

        return $summary;
    }

    /**
     * Formate les stats journalières
     */
    private function formatDaily(array $rows): array
    {
        $daily = [];

        foreach ($rows as $row) {
            $date = $row['stat_date'];

            if (!isset($daily[$date])) {
                $daily[$date] = [
                    'date' => $date,
                    'events' => [],
                    'totals' => [
                        'count' => 0,
                        'sum_amount' => 0.0,
                    ],
                ];
            }

            $eventType = $row['event_type'];
            $count = (int) $row['count'];
            $amount = (float) $row['sum_amount'];

            $daily[$date]['events'][$eventType] = [
                'count' => $count,
                'sum_amount' => $amount,
                'unique_users' => (int) $row['unique_users'],
            ];

            $daily[$date]['totals']['count'] += $count;
            $daily[$date]['totals']['sum_amount'] += $amount;
        }

        return array_values($daily);
    }

    /**
     * Nettoie un événement pour l'affichage (supprime données sensibles)
     */
    private function sanitizeEventForOutput(array $event): array
    {
        // Supprimer l'IP complète, garder seulement les 2 premiers octets
        if (isset($event['ip'])) {
            $parts = explode('.', $event['ip']);
            if (count($parts) === 4) {
                $event['ip'] = $parts[0] . '.' . $parts[1] . '.x.x';
            }
        }

        // Supprimer le user_agent complet
        unset($event['user_agent']);

        // Supprimer les données chiffrées si présentes
        unset($event['encrypted_name']);
        unset($event['encrypted_email']);

        return $event;
    }
}
