namespace PSLauncher.Models;

public sealed class LocalConfig
{
    public int SchemaVersion { get; set; } = 1;
    public string ServerBaseUrl { get; set; } = "https://asterionvr.com/PS_Launcher/api";
    public string InstallRoot { get; set; } = string.Empty;
    public string? LastLaunchedVersion { get; set; }

    /// <summary>
    /// Code de langue de l'UI : "auto" (suit la langue Windows), "fr", "en",
    /// "zh", "th" ou "ar". Tout changement nécessite un redémarrage du launcher.
    /// </summary>
    public string Language { get; set; } = "auto";

    /// <summary>
    /// Nombre de connexions HTTP parallèles utilisées pour télécharger un build.
    /// 1 = comportement legacy single-connection. 4-8 contourne le throttling
    /// per-connection d'Apache/OVH mutualisé. 6 = défaut conservateur pour
    /// permettre plusieurs PCs en simultané sans saturer le pool PHP-FPM
    /// (gate.php tient un worker par segment pendant tout le DL — 16 segments
    /// × 2 PCs = 32 workers, ce qui dépasse la limite OVH mutualisé ~30).
    /// Push à 12/16 sur un serveur dédié ou si un seul poste télécharge.
    /// </summary>
    public int ParallelDownloadSegments { get; set; } = 6;

    /// <summary>
    /// Si false, ignore la vérification SHA-256 même si le manifest l'exige.
    /// Utile sur les machines lentes ou pour gagner 30 s-3 min en fin de DL d'un gros build.
    /// La sécurité repose alors uniquement sur la signature Ed25519 du manifest + HTTPS,
    /// ce qui reste robuste.
    /// </summary>
    public bool VerifyDownloadHash { get; set; } = true;

    /// <summary>
    /// URL de l'outil de reporting local (page web embarquée dans le launcher,
    /// onglet "Report"). Par défaut pointe vers l'install standard ASTERION
    /// dans XAMPP. Surchargeable dans Settings → Avancés.
    /// </summary>
    public string ReportUrl { get; set; } = "http://localhost/ProserveReport/";

    /// <summary>
    /// URL de la documentation locale (onglet "Documentation"). Pointe par défaut
    /// vers l'install standard ASTERION dans XAMPP. Surchargeable dans Settings →
    /// Avancés. Si vide, l'onglet affiche un placeholder.
    /// </summary>
    public string DocumentationUrl { get; set; } = "http://localhost/ProserveDoc/";

    public LicenseConfig License { get; set; } = new();

    /// <summary>
    /// Configuration du déploiement de l'outil Report (page web locale embarquée
    /// dans l'onglet "Statistiques") vers la racine XAMPP htdocs.
    /// Le launcher copie le dossier <c>_report/</c> bundled dans chaque ZIP
    /// PROSERVE vers <c>{HtdocsRoot}\{FolderName}</c> à chaque install.
    /// </summary>
    public ReportToolConfig ReportTool { get; set; } = new();

    /// <summary>
    /// Configuration du déploiement de l'outil Documentation (page web locale
    /// embarquée dans l'onglet "Documentation"). Même mécanisme que ReportTool :
    /// copie de <c>_doc/</c> bundled dans le ZIP PROSERVE vers
    /// <c>{HtdocsRoot}\{FolderName}</c> avec backups horodatés et revert.
    /// </summary>
    public DocToolConfig DocTool { get; set; } = new();

    /// <summary>
    /// Configuration du déploiement de l'API PHP de stats PROSERVE. Même mécanisme
    /// que ReportTool / DocTool : copie de <c>_api/</c> bundled dans le ZIP PROSERVE
    /// vers <c>{HtdocsRoot}\{FolderName}</c> avec backups horodatés et revert.
    /// </summary>
    public ApiToolConfig ApiTool { get; set; } = new();

    /// <summary>
    /// Configuration du merge des settings SteamVR. Si le ZIP contient un sous-dossier
    /// <c>_steamvr/</c> avec <c>steamvr.vrsettings</c>, le launcher arrête SteamVR
    /// et Vive Business Streaming, fusionne les blocs racine du JSON source dans le
    /// fichier SteamVR de l'utilisateur (replace si la clé existe, ajoute sinon),
    /// puis laisse SteamVR redémarrer naturellement quand l'utilisateur lance PROSERVE.
    /// </summary>
    public SteamVrConfig SteamVr { get; set; } = new();

    /// <summary>
    /// Cache LAN P2P : permet à un PC du LAN de partager les ZIPs déjà téléchargés
    /// aux autres PCs (mode serveur), et/ou de chercher les ZIPs sur le LAN avant
    /// de fallback sur OVH (mode client). Les deux flags sont indépendants.
    /// Économise des dizaines de Go de bande passante Internet pour les salles
    /// PROSERVE multi-postes.
    /// </summary>
    public LanCacheConfig LanCache { get; set; } = new();

    /// <summary>
    /// Mode auto : lance automatiquement une version sélectionnée au démarrage du
    /// launcher, et relance si PROSERVE quitte (avec un grace period 3-5s pendant
    /// lequel l'opérateur peut annuler pour reprendre la main).
    /// </summary>
    public AutoModeConfig AutoMode { get; set; } = new();

    /// <summary>
    /// Surveillance d'état système affichée en bandeau sous la top bar
    /// (pings d'IP, processus requis comme SteamVR / Vive Business Streaming…).
    /// Chaque check a un statut Vert (OK) / Orange (warn, ex. ping élevé) / Rouge
    /// (KO). Tooltip détaillé au survol.
    /// </summary>
    public HealthChecksConfig HealthChecks { get; set; } = new();

    /// <summary>
    /// Connexion à la base MySQL locale (XAMPP) utilisée par PROSERVE pour ses
    /// statistiques. Le launcher s'en sert pour appliquer les migrations bundled
    /// dans <c>_migrations/</c> au moment de l'install d'une nouvelle version.
    /// </summary>
    public DatabaseConfig Database { get; set; } = new();
}

/// <summary>
/// Paramètres de connexion à la base MySQL locale. Les valeurs par défaut
/// correspondent à un XAMPP standard fraîchement installé. Override possible
/// via Settings → Base de données.
/// </summary>
public sealed class DatabaseConfig
{
    public string Host { get; set; } = "localhost";
    public uint Port { get; set; } = 3306;
    public string User { get; set; } = "root";

    /// <summary>
    /// Mot de passe MySQL stocké chiffré DPAPI (CurrentUser scope) en base64.
    /// Vide = pas de password (config XAMPP par défaut). NE PAS écrire en clair.
    /// </summary>
    public string? EncryptedPassword { get; set; }

    /// <summary>
    /// Nom de la base de données qui contient les tables de PROSERVE.
    /// Default "proserveapi" — c'est le nom utilisé par le script d'install ASTERION
    /// qui provisionne XAMPP sur les machines clients. À surcharger seulement si la DB
    /// a été créée sous un autre nom.
    /// </summary>
    public string Database { get; set; } = "proserveapi";

    /// <summary>
    /// Si true, le launcher applique automatiquement les migrations SQL bundled
    /// dans <c>_migrations/</c> de chaque ZIP, juste après l'extraction.
    /// Sécurité : on n'applique JAMAIS sans cette opt-in (au cas où certains users
    /// auraient une config DB exotique non gérée par le defaut).
    /// </summary>
    public bool AutoApplyMigrations { get; set; } = true;
}

/// <summary>
/// Paramètres de déploiement de l'outil Report local.
/// Le launcher cherche un sous-dossier <c>_report/</c> dans chaque ZIP PROSERVE
/// installé et le copie vers <c>{HtdocsRoot}\{FolderName}</c> via un rename
/// atomique (deploy en .new puis swap). L'URL de l'onglet Statistiques pointe
/// vers <c>http://localhost/{FolderName}/</c> ; les deux doivent matcher.
/// </summary>
public sealed class ReportToolConfig
{
    public string HtdocsRoot { get; set; } = @"C:\xampp\htdocs";
    public string FolderName { get; set; } = "ProserveReport";
    public bool AutoDeploy { get; set; } = true;

    /// <summary>
    /// Nombre de backups horodatés à conserver dans htdocs après chaque deploy.
    /// Format des dossiers : <c>{FolderName}.backup-yyyyMMdd-HHmmss</c>.
    /// Permet le revert via Settings si le nouveau deploy a un bug visible.
    /// 3 par défaut = couvre les dernières releases sans grossir trop le disque.
    /// 0 = pas de backup (retourne au comportement v0.12.0).
    /// </summary>
    public int MaxBackups { get; set; } = 3;
}

/// <summary>
/// Mêmes paramètres que <see cref="ReportToolConfig"/> mais pour la
/// documentation locale (sous-dossier <c>_doc/</c> du ZIP, déployé vers
/// <c>{HtdocsRoot}\ProserveDoc</c> par défaut).
/// </summary>
public sealed class DocToolConfig
{
    public string HtdocsRoot { get; set; } = @"C:\xampp\htdocs";
    public string FolderName { get; set; } = "ProserveDoc";
    public bool AutoDeploy { get; set; } = true;
    public int MaxBackups { get; set; } = 3;
}

/// <summary>
/// Mêmes paramètres que <see cref="ReportToolConfig"/> mais pour l'API PHP de
/// stats (sous-dossier <c>_api/</c> du ZIP, déployé vers
/// <c>{HtdocsRoot}\proserve</c> par défaut — c'est l'emplacement standard où
/// PROSERVE s'attend à trouver son API côté client, en minuscule).
/// </summary>
public sealed class ApiToolConfig
{
    public string HtdocsRoot { get; set; } = @"C:\xampp\htdocs";
    public string FolderName { get; set; } = "proserve";
    public bool AutoDeploy { get; set; } = true;
    public int MaxBackups { get; set; } = 3;
}

/// <summary>
/// Configuration du cache LAN P2P. Symétrique : <see cref="ServerEnabled"/> et
/// <see cref="ClientEnabled"/> sont indépendants — un PC peut être les deux,
/// l'un ou l'autre, ou aucun. Les ports default sont choisis hors range bien
/// connu (47623 HTTP, 47624 UDP discovery) pour éviter les conflits.
/// </summary>
public sealed class LanCacheConfig
{
    /// <summary>Active le serveur HTTP local qui expose les ZIPs cachés aux peers du LAN.</summary>
    public bool ServerEnabled { get; set; } = false;

    /// <summary>Active le mode client : avant de DL depuis OVH, probe les peers LAN.</summary>
    public bool ClientEnabled { get; set; } = true;

    /// <summary>Port TCP du serveur HTTP local. Doit être autorisé par le firewall (rules posées par l'installeur).</summary>
    public int ServerPort { get; set; } = 47623;

    /// <summary>Port UDP utilisé pour le broadcast d'auto-découverte des peers.</summary>
    public int DiscoveryPort { get; set; } = 47624;

    /// <summary>
    /// URLs peers ajoutées manuellement (override / fallback de l'auto-discovery).
    /// Format : <c>http://10.0.0.5:47623</c> (sans trailing slash). Probées EN PLUS
    /// des peers découverts auto, après ceux-ci.
    ///
    /// Default <c>http://10.0.4.100:47623</c> = IP fixe du PC serveur ASTERION
    /// dans toutes les salles VR PROSERVE. Permet la découverte instantanée au
    /// tout premier démarrage du launcher (avant que le 1er beacon UDP n'arrive).
    /// </summary>
    public List<string> ManualPeerUrls { get; set; } = new() { "http://10.0.4.100:47623" };

    /// <summary>
    /// Nombre de ZIPs à conserver en cache local (les N plus récents par mtime).
    /// Default 2 = la version courante + la précédente. Permet rollback instantané
    /// et le PC serveur peut servir 2 versions aux clients qui n'ont pas encore migré.
    /// Les ZIPs des versions actuellement installées ne sont JAMAIS pruned, peu
    /// importe leur âge.
    /// </summary>
    public int MaxCachedVersions { get; set; } = 2;

    /// <summary>
    /// Timeout par probe HTTP <c>/v1/has/</c> sur un peer. Multiplié par le nombre
    /// de peers tentés, c'est le délai max avant fallback OVH. Default 1000 ms ×
    /// max 5 peers = 5 s pire cas avant de passer à OVH (acceptable).
    /// </summary>
    public int ProbeTimeoutMs { get; set; } = 1000;
}

public sealed class HealthChecksConfig
{
    /// <summary>Période de re-vérification, en secondes. 0 = désactivé.</summary>
    public int RefreshIntervalSeconds { get; set; } = 10;

    /// <summary>
    /// Liste des checks à effectuer. Defaults = exemples typiques setup VR
    /// (SteamVR + Vive Business Streaming + ping casque). L'utilisateur édite
    /// la liste via <c>%LocalAppData%\PSLauncher\config.json</c> pour ajuster
    /// les noms de processus, IPs et seuils ; un éditeur Settings est prévu
    /// en v2 si le besoin est récurrent.
    /// </summary>
    public List<HealthCheckEntry> Checks { get; set; } = new()
    {
        new HealthCheckEntry
        {
            Name = "SteamVR",
            Icon = "🎮",
            Kind = "Process",
            Target = "vrserver",
        },
        new HealthCheckEntry
        {
            Name = "Vive Business Streaming",
            Icon = "📡",
            Kind = "Process",
            Target = "rrserver",
        },
        // PAS de check « casque connecté » par défaut.
        //
        // Pourquoi : avec Vive Business Streaming dans la pile, le driver Vive
        // ment à SteamVR — il prétend qu'un HMD est présent même quand la
        // session Wi-Fi avec le casque physique est tombée. Donc tous les
        // signaux qui passent par SteamVR (VR_IsHmdPresent, vtable IVRSystem,
        // logs vrserver) sont contaminés par ce mensonge et ne reflètent pas
        // l'état réel du casque.
        //
        // À traiter en Phase 2 via un signal authoritative qui ne passe PAS
        // par SteamVR (pistes brainstormées : connexions TCP/UDP de
        // HtcConnectionUtility via GetExtendedTcpTable, endpoint local de
        // Vive Console, compteurs IO du process, ARP table avec OUIs HTC,
        // logs Vive Streaming directs). Ajout d'un Kind dédié à ce moment-là.
    };
}

public sealed class HealthCheckEntry
{
    /// <summary>Libellé affiché dans le bandeau, ex. "SteamVR".</summary>
    public string Name { get; set; } = "";

    /// <summary>Pictogramme emoji affiché à côté du nom, ex. "🎮", "📡", "🥽".</summary>
    public string Icon { get; set; } = "🔌";

    /// <summary>"Process" ou "Ping" (case-insensitive).</summary>
    public string Kind { get; set; } = "Process";

    /// <summary>
    /// Selon Kind : nom du processus (sans .exe) pour Process,
    /// IP/hostname pour Ping. Vide = check ignoré (statut Unknown).
    /// </summary>
    public string Target { get; set; } = "";

    /// <summary>
    /// Période entre 2 vérifications de CE check, en millisecondes. Indépendant
    /// pour chaque entry car un Process check est rapide (~1ms) et peut tourner
    /// toutes les secondes, alors qu'un Ping coûte une requête réseau et peut
    /// tourner toutes les 5-10 s. Default 2000 ms.
    /// </summary>
    public int RefreshIntervalMs { get; set; } = 2000;

    /// <summary>Pour Ping : RTT au-dessus duquel le statut passe en Warning (Orange). Default 50 ms.</summary>
    public int? PingWarnMs { get; set; } = 50;

    /// <summary>Pour Ping : RTT au-dessus duquel le statut passe en Error (Rouge). Default 200 ms.</summary>
    public int? PingErrorMs { get; set; } = 200;

    /// <summary>Pour Ping : timeout de la requête ICMP. Default 1 s.</summary>
    public int? PingTimeoutMs { get; set; } = 1000;
}

/// <summary>
/// Configuration du merge SteamVR au moment de l'install d'une nouvelle version
/// PROSERVE. Le ZIP peut contenir un dossier <c>_steamvr/steamvr.vrsettings</c>
/// avec des blocs racine spécifiques au jeu (system.generated.openxr.*, trackers,
/// etc.). Si <see cref="AutoMerge"/> est true, le launcher :
/// <list type="number">
///   <item>tue SteamVR + Vive Business Streaming (qui le relance auto)</item>
///   <item>localise <c>steamvr.vrsettings</c> via le registre Steam (ou <see cref="SettingsFilePathOverride"/>)</item>
///   <item>parse les deux JSON, remplace chaque clé racine du source dans la cible (ou ajoute si absente)</item>
///   <item>écrit le résultat atomiquement (rename .tmp → final)</item>
/// </list>
/// SteamVR redémarrera naturellement quand l'opérateur relance PROSERVE après
/// l'install.
/// </summary>
public sealed class SteamVrConfig
{
    /// <summary>
    /// Si true, le launcher applique le merge automatiquement à chaque install
    /// quand <c>_steamvr/steamvr.vrsettings</c> est présent dans le ZIP. Default
    /// true (transparent pour l'opérateur), mais peut être désactivé pour les
    /// machines de dev où le settings doit rester intact.
    /// </summary>
    public bool AutoMerge { get; set; } = true;

    /// <summary>
    /// Path explicite vers le <c>steamvr.vrsettings</c> à modifier. Null →
    /// auto-détection via :
    /// <list type="number">
    ///   <item>HKCU\Software\Valve\Steam → SteamPath → config\steamvr.vrsettings</item>
    ///   <item>HKLM\SOFTWARE\WOW6432Node\Valve\Steam → InstallPath → idem</item>
    ///   <item>fallback %ProgramFilesX86%\Steam\config\steamvr.vrsettings</item>
    /// </list>
    /// </summary>
    public string? SettingsFilePathOverride { get; set; }

    /// <summary>
    /// Processus à tuer avant le merge (sans le suffixe .exe). Default = la
    /// liste typique SteamVR + Vive Business Streaming. Vive Business Streaming
    /// relance auto SteamVR ⇒ il faut le tuer EN PREMIER. Editable depuis
    /// Settings pour les setups exotiques (autres runtimes XR).
    /// </summary>
    public List<string> ProcessesToKill { get; set; } = new()
    {
        // Vive Business Streaming (à tuer en premier — relance SteamVR sinon)
        "VIVE Business Streaming",
        "RRConsole",
        "RRServer",
        "HtcConnectionUtility",
        // SteamVR
        "vrserver",
        "vrmonitor",
        "vrcompositor",
        "vrdashboard",
        "vrwebhelper",
        "vrstartup",
    };
}

/// <summary>
/// Configuration du mode auto. <see cref="FeatureEnabled"/> est le kill-switch
/// global — quand false, les boutons AUTO ne sont pas affichés sur les rows et
/// le launcher ne fait jamais d'auto-launch. Quand true, l'opérateur peut
/// désigner UNE version via le bouton AUTO ; le launcher lance cette version
/// au démarrage et la relance si elle quitte (après un grace period).
/// </summary>
public sealed class AutoModeConfig
{
    /// <summary>
    /// Master toggle : si false, le mode auto est désactivé partout (boutons AUTO
    /// masqués, pas d'auto-launch). Réglable dans Settings → Avancés.
    /// </summary>
    public bool FeatureEnabled { get; set; } = false;

    /// <summary>
    /// Version courante désignée pour l'auto-launch (le bouton AUTO orange sur cette
    /// row). Null = aucune désignée (mode auto inactif même si FeatureEnabled=true).
    /// </summary>
    public string? SelectedVersion { get; set; }

    /// <summary>
    /// Délai en secondes affiché dans le popup modal de relance après que PROSERVE
    /// ait quitté. Pendant ce délai l'opérateur peut cliquer Annuler pour reprendre
    /// la main. Clamped 3-10s côté UI.
    /// </summary>
    public int GracePeriodSeconds { get; set; } = 5;

    /// <summary>
    /// Arguments CLI passés à PROSERVE_UE_*.exe. Format produit :
    /// si <c>Value</c> est null/vide : <c>-{Key}</c> (flag seul)
    /// sinon : <c>-{Key}={Value}</c>
    /// Exemples : <c>-autoconnect</c>, <c>-login=Jerome2</c>, <c>-ipserver=10.0.4.100</c>
    /// </summary>
    public List<AutoModeArg> Args { get; set; } = new();

    /// <summary>
    /// Si true, le launcher attend que TOUS les indicateurs de santé système
    /// (<see cref="LocalConfig.HealthChecks"/>) soient en <c>HealthSeverity.Ok</c>
    /// avant de démarrer le countdown de lancement automatique. Utile quand
    /// PROSERVE dépend d'un serveur API, d'un MySQL local, ou de SteamVR qui
    /// peut mettre quelques secondes à se stabiliser au boot. Pendant l'attente,
    /// l'opérateur voit un dialog "Vérification de la santé système" avec un
    /// bouton Annuler ; le countdown ne démarre qu'une fois tous les indicateurs
    /// au vert. Si la liste de health checks est vide, ce flag n'a aucun effet
    /// (rien à attendre → countdown immédiat).
    /// </summary>
    public bool WaitForHealthChecks { get; set; } = false;
}

/// <summary>Une paire clé/valeur d'argument CLI pour PROSERVE. Value optionnel.</summary>
public sealed class AutoModeArg
{
    public string Key { get; set; } = string.Empty;
    public string? Value { get; set; }
}

public sealed class LicenseConfig
{
    public string? EncryptedKey { get; set; }

    /// <summary>
    /// Date UTC de la dernière validation online réussie. Utilise <c>response.ServerTime</c>
    /// (signé Ed25519) plutôt que l'horloge locale, donc non manipulable par l'utilisateur.
    /// Sert de point de départ au calcul d'expiration du cache offline.
    /// </summary>
    public DateTime? LastValidationAt { get; set; }

    public DateTime? CachedEntitlementUntil { get; set; }
    public string? CachedOwnerName { get; set; }

    /// <summary>
    /// Channel de distribution attribué à cette license côté serveur (NULL =
    /// aucun ⇒ accès uniquement aux versions taggées 'default'). Persisté ici
    /// pour que <see cref="ILicenseService.GetCached"/> restaure ce champ entre
    /// deux lancements du launcher — sans ça, le channelProvider du
    /// ManifestService retombait sur null à chaque relance et le client
    /// fetchait toujours le manifest default.
    /// </summary>
    public string? CachedChannel { get; set; }

    /// <summary>
    /// Flag <c>canSeeBetas</c> de la license. Persisté pour la même raison
    /// que <see cref="CachedChannel"/> : restauration entre deux lancements.
    /// </summary>
    public bool CachedCanSeeBetas { get; set; }

    /// <summary>
    /// Hash SHA-256 du password de verrouillage des Settings avancés associé à
    /// cette license (NULL = pas de verrouillage). Persisté entre les lancements
    /// pour que le verrouillage reste actif même hors-ligne, jusqu'à expiration
    /// du cache offline (100 jours).
    /// </summary>
    public string? CachedSettingsLockPasswordHash { get; set; }

    /// <summary>
    /// Statut tel que renvoyé par le serveur lors de la dernière validation : "valid",
    /// "expired", "revoked", "invalid", "machine_limit_exceeded". Persisté pour qu'une
    /// révocation effectuée pendant que le user est offline soit toujours visible
    /// même après son expiration de cache local. Le calcul "valid → expired" basé
    /// sur la date est aussi appliqué côté lecture (sanity) si le temps a passé.
    /// </summary>
    public string? CachedStatus { get; set; }

    /// <summary>
    /// Compteur monotone : date UTC la plus récente jamais observée par le launcher
    /// sur cette machine. Mise à jour à chaque démarrage si <c>now</c> est postérieur.
    /// Sert d'anti-rollback : si l'utilisateur recule l'horloge PC, on détecte
    /// (now &lt; LastSeenUtc - grace) et on invalide le cache → forçage d'une
    /// re-validation online la prochaine fois qu'il sera connecté.
    /// </summary>
    public DateTime? LastSeenUtc { get; set; }
}
