<?php
namespace App\Core;

use DateInterval;
use DateTimeImmutable;
use PDO;

class ModuleManager
{
    private const TRIAL_PERIOD_SPEC = 'P1M';

    public static function registry(): array
    {
        return [
            'prospect_clients' => [
                'key' => 'prospect_clients',
                'name' => 'Gestion Prospects & Clients',
                'description' => 'Enregistrement des prospects, validation du paiement et transmission vers le workflow de raccordement client.',
                'route_path' => '/prospect-clients',
                'icon_class' => 'fa-users-viewfinder',
                'category' => 'operations',
                'permission_prefixes' => ['prospect_clients'],
            ],
            'raccordement_clients' => [
                'key' => 'raccordement_clients',
                'name' => 'Raccordement Clients',
                'description' => 'Gestion des demandes, études et fiches terrain de raccordement clients.',
                'route_path' => '/raccordement-clients',
                'icon_class' => 'fa-plug-circle-bolt',
                'category' => 'operations',
                'permission_prefixes' => ['raccordement_clients'],
            ],
            'otb_management' => [
                'key' => 'otb_management',
                'name' => 'Gestion des boitiers OTB',
                'description' => 'Gestion du referentiel OTB, des seuils d\'alerte et de la saturation des ports.',
                'route_path' => '/otb-management',
                'icon_class' => 'fa-boxes-packing',
                'category' => 'operations',
                'permission_prefixes' => ['otb_management'],
            ],
        ];
    }

    public static function ensureSchema(PDO $pdo): void
    {
        $pdo->exec("CREATE TABLE IF NOT EXISTS app_modules (
            module_key VARCHAR(80) NOT NULL PRIMARY KEY,
            name VARCHAR(150) NOT NULL,
            description TEXT NULL,
            route_path VARCHAR(190) NULL,
            icon_class VARCHAR(80) NULL,
            category VARCHAR(60) NULL,
            installed TINYINT(1) NOT NULL DEFAULT 0,
            enabled TINYINT(1) NOT NULL DEFAULT 0,
            installed_at DATETIME NULL,
            trial_started_at DATETIME NULL,
            trial_expires_at DATETIME NULL,
            activated_at DATETIME NULL,
            activated_by INT NULL,
            uninstalled_at DATETIME NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        $stmt = $pdo->prepare("INSERT INTO app_modules (module_key, name, description, route_path, icon_class, category)
            VALUES (?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE
                name = VALUES(name),
                description = VALUES(description),
                route_path = VALUES(route_path),
                icon_class = VALUES(icon_class),
                category = VALUES(category)");

        foreach (self::registry() as $module) {
            $stmt->execute([
                $module['key'],
                $module['name'],
                $module['description'],
                $module['route_path'],
                $module['icon_class'],
                $module['category'],
            ]);
        }
    }

    public static function all(PDO $pdo): array
    {
        self::ensureSchema($pdo);
        $rows = $pdo->query('SELECT * FROM app_modules ORDER BY name ASC')->fetchAll(PDO::FETCH_ASSOC) ?: [];
        $indexed = [];
        foreach ($rows as $row) {
            $indexed[(string)($row['module_key'] ?? '')] = $row;
        }

        $modules = [];
        foreach (self::registry() as $key => $meta) {
            $modules[] = self::hydrateModule($meta, $indexed[$key] ?? null);
        }

        return $modules;
    }

    public static function find(PDO $pdo, string $moduleKey): ?array
    {
        self::ensureSchema($pdo);
        $registry = self::registry();
        if (!isset($registry[$moduleKey])) {
            return null;
        }

        $stmt = $pdo->prepare('SELECT * FROM app_modules WHERE module_key = ? LIMIT 1');
        $stmt->execute([$moduleKey]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;

        return self::hydrateModule($registry[$moduleKey], $row);
    }

    public static function isVisible(PDO $pdo, string $moduleKey): bool
    {
        $module = self::find($pdo, $moduleKey);
        return (bool)($module['installed'] ?? false);
    }

    public static function install(PDO $pdo, string $moduleKey): void
    {
        $module = self::find($pdo, $moduleKey);
        if (!$module) {
            return;
        }

        $now = new DateTimeImmutable('now');
        $trialStartedAt = !empty($module['trial_started_at'])
            ? new DateTimeImmutable((string)$module['trial_started_at'])
            : $now;
        $trialExpiresAt = !empty($module['trial_expires_at'])
            ? new DateTimeImmutable((string)$module['trial_expires_at'])
            : $trialStartedAt->add(new DateInterval(self::TRIAL_PERIOD_SPEC));
        $installedAt = !empty($module['installed_at'])
            ? new DateTimeImmutable((string)$module['installed_at'])
            : $now;

        $stmt = $pdo->prepare('UPDATE app_modules
            SET installed = 1,
                enabled = 1,
                installed_at = ?,
                trial_started_at = ?,
                trial_expires_at = ?,
                uninstalled_at = NULL,
                updated_at = NOW()
            WHERE module_key = ?');
        $stmt->execute([
            $installedAt->format('Y-m-d H:i:s'),
            $trialStartedAt->format('Y-m-d H:i:s'),
            $trialExpiresAt->format('Y-m-d H:i:s'),
            $moduleKey,
        ]);
    }

    public static function setEnabled(PDO $pdo, string $moduleKey, bool $enabled): void
    {
        self::ensureSchema($pdo);
        $stmt = $pdo->prepare('UPDATE app_modules SET enabled = ?, updated_at = NOW() WHERE module_key = ?');
        $stmt->execute([$enabled ? 1 : 0, $moduleKey]);
    }

    public static function uninstall(PDO $pdo, string $moduleKey): void
    {
        self::ensureSchema($pdo);
        $registry = self::registry();
        $module = $registry[$moduleKey] ?? null;

        try {
            if (!$pdo->inTransaction()) {
                $pdo->beginTransaction();
            }

            $stmt = $pdo->prepare('UPDATE app_modules
                SET installed = 0,
                    enabled = 0,
                    uninstalled_at = NOW(),
                    updated_at = NOW()
                WHERE module_key = ?');
            $stmt->execute([$moduleKey]);

            self::cleanupPermissionAttachments($pdo, self::permissionPrefixesForModule($moduleKey, $module));

            if ($pdo->inTransaction()) {
                $pdo->commit();
            }
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $e;
        }
    }

    private static function permissionPrefixesForModule(string $moduleKey, ?array $module): array
    {
        $prefixes = $module['permission_prefixes'] ?? [$moduleKey];
        if (!is_array($prefixes)) {
            $prefixes = [$moduleKey];
        }

        $prefixes = array_values(array_unique(array_filter(array_map(static fn($value): string => trim((string)$value), $prefixes))));
        return $prefixes !== [] ? $prefixes : [$moduleKey];
    }

    private static function cleanupPermissionAttachments(PDO $pdo, array $permissionPrefixes): void
    {
        if ($permissionPrefixes === []) {
            return;
        }

        self::cleanupJsonRolePermissions($pdo, $permissionPrefixes);
        self::cleanupRelationalPermissions($pdo, $permissionPrefixes);
    }

    private static function cleanupJsonRolePermissions(PDO $pdo, array $permissionPrefixes): void
    {
        if (!self::tableHasColumn($pdo, 'roles', 'permissions')) {
            return;
        }

        $rows = $pdo->query('SELECT id, permissions FROM roles')->fetchAll(PDO::FETCH_ASSOC) ?: [];
        $update = $pdo->prepare('UPDATE roles SET permissions = ? WHERE id = ?');

        foreach ($rows as $row) {
            $roleId = (int)($row['id'] ?? 0);
            if ($roleId <= 0) {
                continue;
            }

            $permissions = json_decode((string)($row['permissions'] ?? ''), true);
            if (!is_array($permissions)) {
                continue;
            }

            $filtered = array_values(array_filter($permissions, static function ($permission) use ($permissionPrefixes): bool {
                $key = trim((string)$permission);
                if ($key === '') {
                    return false;
                }

                foreach ($permissionPrefixes as $prefix) {
                    if ($key === $prefix || str_starts_with($key, $prefix . '.')) {
                        return false;
                    }
                }

                return true;
            }));

            if ($filtered === array_values($permissions)) {
                continue;
            }

            $update->execute([json_encode($filtered, JSON_UNESCAPED_UNICODE), $roleId]);
        }
    }

    private static function cleanupRelationalPermissions(PDO $pdo, array $permissionPrefixes): void
    {
        if (!self::tableExists($pdo, 'permissions') || !self::tableHasColumn($pdo, 'permissions', 'key_name')) {
            return;
        }

        $conditions = [];
        $params = [];
        foreach ($permissionPrefixes as $prefix) {
            $conditions[] = 'key_name = ? OR key_name LIKE ?';
            $params[] = $prefix;
            $params[] = $prefix . '.%';
        }

        if ($conditions === []) {
            return;
        }

        $select = $pdo->prepare('SELECT id FROM permissions WHERE ' . implode(' OR ', $conditions));
        $select->execute($params);
        $permissionIds = array_values(array_filter(array_map('intval', $select->fetchAll(PDO::FETCH_COLUMN) ?: [])));
        if ($permissionIds === []) {
            return;
        }

        if (self::tableExists($pdo, 'role_permissions')) {
            $placeholders = implode(',', array_fill(0, count($permissionIds), '?'));
            $deleteRoleLinks = $pdo->prepare('DELETE FROM role_permissions WHERE permission_id IN (' . $placeholders . ')');
            $deleteRoleLinks->execute($permissionIds);
        }

        $placeholders = implode(',', array_fill(0, count($permissionIds), '?'));
        $deletePermissions = $pdo->prepare('DELETE FROM permissions WHERE id IN (' . $placeholders . ')');
        $deletePermissions->execute($permissionIds);
    }

    private static function tableExists(PDO $pdo, string $tableName): bool
    {
        $stmt = $pdo->prepare('SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? LIMIT 1');
        $stmt->execute([$tableName]);
        return (bool)$stmt->fetchColumn();
    }

    private static function tableHasColumn(PDO $pdo, string $tableName, string $columnName): bool
    {
        $stmt = $pdo->prepare('SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1');
        $stmt->execute([$tableName, $columnName]);
        return (bool)$stmt->fetchColumn();
    }

    public static function buildBlockedViewData(?array $module): array
    {
        $moduleName = (string)($module['name'] ?? 'Module');
        $statusCode = (string)($module['status_code'] ?? 'blocked');
        $title = match ($statusCode) {
            'not_installed' => 'Module non installé',
            'disabled' => 'Module désactivé',
            'trial_expired' => 'Activation requise',
            default => 'Accès au module bloqué',
        };

        $message = match ($statusCode) {
            'not_installed' => 'Ce module n’est pas installé sur cette plateforme. Un administrateur doit d’abord l’installer dans Gestion des modules.',
            'disabled' => 'Ce module est actuellement désactivé. Un administrateur doit le réactiver dans Gestion des modules avant son utilisation.',
            'trial_expired' => 'La période d’essai d’un mois est écoulée. Ce module doit être activé par le développeur uniquement après paiement des modules.',
            default => 'Ce module n’est pas disponible pour le moment.',
        };

        return [
            'title' => $title,
            'module' => $module,
            'moduleName' => $moduleName,
            'blockingMessage' => $message,
        ];
    }

    private static function hydrateModule(array $meta, ?array $row): array
    {
        $now = new DateTimeImmutable('now');
        $installed = (int)($row['installed'] ?? 0) === 1;
        $enabled = (int)($row['enabled'] ?? 0) === 1;
        $activatedAt = trim((string)($row['activated_at'] ?? ''));
        $trialStartedAt = trim((string)($row['trial_started_at'] ?? ''));
        $trialExpiresAt = trim((string)($row['trial_expires_at'] ?? ''));
        $isActivated = $activatedAt !== '';
        $trialExpired = false;
        $trialRemainingDays = null;

        if ($trialExpiresAt !== '') {
            try {
                $expiresAt = new DateTimeImmutable($trialExpiresAt);
                $trialExpired = $expiresAt < $now;
                $remainingSeconds = $expiresAt->getTimestamp() - $now->getTimestamp();
                $trialRemainingDays = $remainingSeconds > 0 ? (int)ceil($remainingSeconds / 86400) : 0;
            } catch (\Throwable $e) {
                $trialExpired = false;
                $trialRemainingDays = null;
            }
        }

        $statusCode = 'not_installed';
        if ($installed && !$enabled) {
            $statusCode = 'disabled';
        } elseif ($installed && $enabled && $isActivated) {
            $statusCode = 'activated';
        } elseif ($installed && $enabled && $trialExpired) {
            $statusCode = 'trial_expired';
        } elseif ($installed && $enabled) {
            $statusCode = 'trial_active';
        }

        $accessible = $installed && $enabled && ($isActivated || !$trialExpired);

        return array_merge($meta, $row ?? [], [
            'installed' => $installed,
            'enabled' => $enabled,
            'activated' => $isActivated,
            'accessible' => $accessible,
            'trial_started_at' => $trialStartedAt !== '' ? $trialStartedAt : null,
            'trial_expires_at' => $trialExpiresAt !== '' ? $trialExpiresAt : null,
            'trial_remaining_days' => $trialRemainingDays,
            'trial_expired' => $trialExpired,
            'status_code' => $statusCode,
            'status_label' => match ($statusCode) {
                'not_installed' => 'Disponible à l’installation',
                'disabled' => 'Désactivé',
                'activated' => 'Activé',
                'trial_expired' => 'Essai expiré',
                default => 'Essai en cours',
            },
        ]);
    }
}