using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using PSLauncher.Core.Configuration;
using PSLauncher.Core.Downloads;
using PSLauncher.Core.Installations;
using PSLauncher.Core.Licensing;
using PSLauncher.Core.Localization;
using PSLauncher.Core.Migrations;
using PSLauncher.Models;

namespace PSLauncher.App.ViewModels;

public sealed record LanguageOption(string Code, string Name)
{
    // Override pour que la SelectionBox d'un ComboBox affiche le libellé clair
    // (sinon WPF affiche la forme record "LanguageOption { Code = ..., Name = ... }").
    public override string ToString() => Name;
}

public sealed partial class SettingsViewModel : ObservableObject
{
    private readonly IConfigStore _configStore;
    private readonly LocalConfig _config;
    private readonly ILicenseService _licenseService;
    private readonly IDownloadStateStore _downloadStore;
    private readonly IDatabaseMigrationService _migrationService;
    private readonly IInstallationRegistry _registry;
    private readonly HttpClient _http;
    private readonly ILogger<SettingsViewModel> _logger;

    public string LauncherVersion { get; }
    public string MachineId { get; }
    public string LogsDirectory { get; }
    public string CacheDirectory { get; }

    [ObservableProperty] private string _serverBaseUrl;
    [ObservableProperty] private string _installRoot;
    [ObservableProperty] private string _language;
    [ObservableProperty] private string _reportUrl;
    [ObservableProperty] private string _documentationUrl;

    // ----- DB Migration settings -----
    [ObservableProperty] private string _dbHost;
    [ObservableProperty] private uint _dbPort;
    [ObservableProperty] private string _dbUser;
    [ObservableProperty] private string _dbPassword;
    [ObservableProperty] private string _dbName;
    [ObservableProperty] private bool _dbAutoApplyMigrations;
    [ObservableProperty] private string? _dbConnectionStatus;
    [ObservableProperty] private bool _isTestingDb;
    [ObservableProperty] private string? _migrationsReplayStatus;
    [ObservableProperty] private bool _isReplayingMigrations;

    public IReadOnlyList<LanguageOption> AvailableLanguages { get; } =
        PSLauncher.Core.Localization.Strings.Available
            .Select(t => new LanguageOption(t.Code, t.Name))
            .ToList();
    [ObservableProperty] private string? _connectionStatus;
    [ObservableProperty] private bool _isTestingConnection;
    [ObservableProperty] private string _cacheSizeDisplay = "—";

    public string LicenseInfo
    {
        get
        {
            var l = _licenseService.GetCached();
            if (l is null) return Strings.LicenseSummaryNone;
            if (l.Status == "valid")
                return Strings.LicenseSummaryValid(l.OwnerName ?? "—",
                    Strings.FormatDate(l.DownloadEntitlementUntil));
            if (l.Status == "expired")
                return Strings.LicenseSummaryExpired(Strings.FormatDate(l.DownloadEntitlementUntil));
            return $"{l.OwnerName} • {l.Status}";
        }
    }

    public string LicenseIcon
    {
        get
        {
            var l = _licenseService.GetCached();
            if (l is null) return "🔒";
            return l.Status switch
            {
                "valid" when IsExpiringSoon(l) => "⏰",
                "valid"                         => "✓",
                "expired"                       => "⚠",
                "revoked"                       => "🚫",
                _                                => "❓",
            };
        }
    }

    public string LicenseSeverity
    {
        get
        {
            var l = _licenseService.GetCached();
            if (l is null) return "Error";
            return l.Status switch
            {
                "valid" when IsExpiringSoon(l) => "Warning",
                "valid"                         => "Ok",
                _                                => "Error",
            };
        }
    }

    private static bool IsExpiringSoon(LicenseValidationResponse l)
    {
        if (l.DownloadEntitlementUntil is not { } until) return false;
        return (until - DateTime.UtcNow).TotalDays < 30;
    }

    public SettingsViewModel(
        IConfigStore configStore,
        LocalConfig config,
        ILicenseService licenseService,
        IDownloadStateStore downloadStore,
        IDatabaseMigrationService migrationService,
        IInstallationRegistry registry,
        HttpClient http,
        ILogger<SettingsViewModel> logger)
    {
        _configStore = configStore;
        _config = config;
        _licenseService = licenseService;
        _downloadStore = downloadStore;
        _migrationService = migrationService;
        _registry = registry;
        _http = http;
        _logger = logger;

        _serverBaseUrl = config.ServerBaseUrl;
        _installRoot = config.InstallRoot;
        _language = string.IsNullOrWhiteSpace(config.Language) ? "auto" : config.Language;
        _reportUrl = config.ReportUrl;
        _documentationUrl = config.DocumentationUrl;

        _dbHost = config.Database.Host;
        _dbPort = config.Database.Port;
        _dbUser = config.Database.User;
        _dbPassword = DatabasePasswordProtector.Unprotect(config.Database.EncryptedPassword) ?? string.Empty;
        _dbName = config.Database.Database;
        _dbAutoApplyMigrations = config.Database.AutoApplyMigrations;

        LauncherVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "0.0.0";
        MachineId = licenseService.GetMachineId();
        LogsDirectory = App.LogsDirectory;
        CacheDirectory = downloadStore.GetDownloadsDirectory();
        RefreshCacheSize();
    }

    [RelayCommand]
    private async Task TestConnectionAsync()
    {
        ConnectionStatus = "Test en cours…";
        IsTestingConnection = true;
        try
        {
            var url = ServerBaseUrl.TrimEnd('/') + "/health";
            using var resp = await _http.GetAsync(url);
            if (resp.IsSuccessStatusCode)
            {
                var body = await resp.Content.ReadAsStringAsync();
                ConnectionStatus = $"✓ OK ({(int)resp.StatusCode}) — {body.Substring(0, Math.Min(80, body.Length))}";
            }
            else
            {
                ConnectionStatus = $"✗ HTTP {(int)resp.StatusCode}";
            }
        }
        catch (Exception ex)
        {
            ConnectionStatus = $"✗ Erreur : {ex.Message}";
        }
        finally
        {
            IsTestingConnection = false;
        }
    }

    [RelayCommand]
    private void BrowseInstallRoot()
    {
        var dlg = new Microsoft.Win32.OpenFolderDialog
        {
            Title = Strings.SettingsInstallRoot,
            InitialDirectory = Directory.Exists(InstallRoot) ? InstallRoot : Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
        };
        if (dlg.ShowDialog() == true)
        {
            InstallRoot = dlg.FolderName;
        }
    }

    [RelayCommand]
    private void OpenLogsFolder()
    {
        if (Directory.Exists(LogsDirectory))
            Process.Start(new ProcessStartInfo { FileName = LogsDirectory, UseShellExecute = true, Verb = "open" });
    }

    [RelayCommand]
    private void OpenCacheFolder()
    {
        if (Directory.Exists(CacheDirectory))
            Process.Start(new ProcessStartInfo { FileName = CacheDirectory, UseShellExecute = true, Verb = "open" });
    }

    [RelayCommand]
    private void ClearCache()
    {
        var confirm = MessageBox.Show(
            Strings.MsgClearCacheConfirm,
            Strings.MsgBoxConfirm,
            MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
        if (confirm != MessageBoxResult.Yes) return;

        try
        {
            foreach (var f in Directory.EnumerateFiles(CacheDirectory))
            {
                try { File.Delete(f); } catch (Exception ex) { _logger.LogWarning(ex, "Cannot delete {F}", f); }
            }
            RefreshCacheSize();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "ClearCache failed");
            MessageBox.Show(ex.Message, Strings.MsgBoxError, MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }

    [RelayCommand]
    private void CopyMachineId()
    {
        try { Clipboard.SetText(MachineId); }
        catch (Exception ex) { _logger.LogWarning(ex, "Clipboard failed"); }
    }

    [RelayCommand]
    private void Save()
    {
        var languageChanged = !string.Equals(
            (_config.Language ?? "auto").ToLowerInvariant(),
            (Language ?? "auto").ToLowerInvariant(),
            StringComparison.Ordinal);

        _config.ServerBaseUrl = ServerBaseUrl.TrimEnd('/');
        _config.InstallRoot = InstallRoot;
        _config.Language = Language ?? "auto";
        _config.ReportUrl = ReportUrl?.Trim() ?? "";
        _config.DocumentationUrl = DocumentationUrl?.Trim() ?? "";

        // DB settings
        _config.Database.Host = DbHost.Trim();
        _config.Database.Port = DbPort > 0 ? DbPort : 3306;
        _config.Database.User = DbUser.Trim();
        _config.Database.EncryptedPassword = string.IsNullOrEmpty(DbPassword)
            ? null
            : DatabasePasswordProtector.Protect(DbPassword);
        _config.Database.Database = DbName.Trim();
        _config.Database.AutoApplyMigrations = DbAutoApplyMigrations;

        _configStore.Save(_config);
        _logger.LogInformation("Settings saved (language={Lang}, changed={Changed})", _config.Language, languageChanged);

        if (languageChanged)
        {
            // Le launcher doit être relancé pour que les bindings x:Static reflètent
            // la nouvelle langue. Plutôt que de demander à l'utilisateur de fermer/rouvrir,
            // on spawne un cmd qui attend la fermeture puis relance.
            var ok = MessageBox.Show(
                Strings.MsgLanguageRestart,
                Strings.MsgBoxLanguageChange,
                MessageBoxButton.OKCancel, MessageBoxImage.Information, MessageBoxResult.OK);
            if (ok == MessageBoxResult.OK)
            {
                try
                {
                    var exe = System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName;
                    if (!string.IsNullOrEmpty(exe))
                    {
                        var pid = Environment.ProcessId;
                        var cmd = $"/c (echo Wait && timeout /t 1 /nobreak > nul) & start \"\" \"{exe}\"";
                        Process.Start(new ProcessStartInfo
                        {
                            FileName = "cmd.exe",
                            Arguments = cmd,
                            UseShellExecute = false,
                            CreateNoWindow = true,
                        });
                        Application.Current.Shutdown();
                    }
                }
                catch (Exception ex) { _logger.LogError(ex, "Restart on language change failed"); }
            }
        }
    }

    [RelayCommand]
    private void DeactivateLicense()
    {
        var confirm = MessageBox.Show(
            Strings.MsgDeactivateLicenseConfirm,
            Strings.MsgBoxConfirm,
            MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
        if (confirm != MessageBoxResult.Yes) return;
        _licenseService.Clear();
        OnPropertyChanged(nameof(LicenseInfo));
    }

    private void RefreshCacheSize()
    {
        try
        {
            long total = 0;
            if (Directory.Exists(CacheDirectory))
            {
                foreach (var f in Directory.EnumerateFiles(CacheDirectory))
                    total += new FileInfo(f).Length;
            }
            CacheSizeDisplay = FormatSize(total);
        }
        catch { CacheSizeDisplay = "—"; }
    }

    private static string FormatSize(long bytes) => Strings.FormatSize(bytes);

    // ===================== DB MIGRATIONS =====================

    /// <summary>
    /// Test la connexion à la DB MySQL avec les paramètres en cours de saisie.
    /// Persiste temporairement les valeurs sur _config.Database avant le test
    /// pour que le service les voie via son configProvider, puis remet (et le
    /// Save() final écrasera quand l'utilisateur cliquera Enregistrer).
    /// </summary>
    [RelayCommand]
    private async Task TestDbAsync()
    {
        DbConnectionStatus = Strings.SettingsTest + "…";
        IsTestingDb = true;
        // On applique temporairement les valeurs saisies
        var oldHost = _config.Database.Host;
        var oldPort = _config.Database.Port;
        var oldUser = _config.Database.User;
        var oldPwd  = _config.Database.EncryptedPassword;
        var oldDb   = _config.Database.Database;
        try
        {
            _config.Database.Host = DbHost.Trim();
            _config.Database.Port = DbPort > 0 ? DbPort : 3306;
            _config.Database.User = DbUser.Trim();
            _config.Database.EncryptedPassword = string.IsNullOrEmpty(DbPassword)
                ? null
                : DatabasePasswordProtector.Protect(DbPassword);
            _config.Database.Database = DbName.Trim();

            var err = await _migrationService.TestConnectionAsync(CancellationToken.None);
            DbConnectionStatus = err is null ? "✓ OK" : $"✗ {err}";
        }
        catch (Exception ex) { DbConnectionStatus = $"✗ {ex.Message}"; }
        finally
        {
            // Restaure : seul Save() doit écrire pour de bon
            _config.Database.Host = oldHost;
            _config.Database.Port = oldPort;
            _config.Database.User = oldUser;
            _config.Database.EncryptedPassword = oldPwd;
            _config.Database.Database = oldDb;
            IsTestingDb = false;
        }
    }

    /// <summary>
    /// Re-joue les migrations bundled dans le _migrations/ de la version actuellement
    /// installée la plus récente. Utile si l'auto-apply post-install avait échoué (ex.
    /// XAMPP éteint à ce moment-là), ou pour rejouer manuellement après ajout d'une
    /// nouvelle migration côté dev.
    /// </summary>
    [RelayCommand]
    private async Task ReplayMigrationsAsync()
    {
        var installed = _registry.Scan().OrderByDescending(v => v.Version).FirstOrDefault();
        if (installed is null)
        {
            MigrationsReplayStatus = "Aucune version installée — installe d'abord une version avec _migrations/.";
            return;
        }
        IsReplayingMigrations = true;
        MigrationsReplayStatus = $"Réapplication des migrations pour v{installed.Version}…";
        try
        {
            var progress = new Progress<MigrationProgress>(mp =>
            {
                if (mp.Total > 0)
                    MigrationsReplayStatus = $"{mp.Done + 1}/{mp.Total} : {mp.CurrentFilename}";
            });
            // Il faut persister les éventuelles modifs DB avant le test (le service lit la config courante)
            Save();
            var result = await _migrationService.ApplyMigrationsAsync(
                installed.FolderPath, progress, CancellationToken.None);
            if (result.AllSucceeded)
            {
                MigrationsReplayStatus = result.AppliedCount > 0
                    ? $"✓ {result.AppliedCount} migration(s) appliquée(s), {result.SkippedCount} déjà en place."
                    : $"✓ Tout est à jour ({result.SkippedCount} déjà appliquée(s))";
            }
            else
            {
                MigrationsReplayStatus = $"✗ Échec : {result.FailureMessage}";
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Replay migrations failed");
            MigrationsReplayStatus = $"✗ Erreur : {ex.Message}";
        }
        finally
        {
            IsReplayingMigrations = false;
        }
    }
}
