using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using PSLauncher.App.Views;
using Microsoft.Extensions.DependencyInjection;
using PSLauncher.App.Services;
using PSLauncher.Core.Configuration;
using PSLauncher.Core.Downloads;
using PSLauncher.Core.Installations;
using PSLauncher.Core.Licensing;
using PSLauncher.Core.Manifests;
using PSLauncher.Core.Process;
using PSLauncher.Core.Updates;
using PSLauncher.Models;

namespace PSLauncher.App.ViewModels;

public sealed partial class MainViewModel : ObservableObject
{
    private readonly IInstallationRegistry _registry;
    private readonly IProcessLauncher _processLauncher;
    private readonly IConfigStore _configStore;
    private readonly LocalConfig _config;
    private readonly IManifestService _manifestService;
    private readonly IUpdateChecker _updateChecker;
    private readonly IDownloadManager _downloadManager;
    private readonly IZipInstaller _zipInstaller;
    private readonly ILicenseService _licenseService;
    private readonly IToastService _toastService;
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<MainViewModel> _logger;

    private LicenseValidationResponse? _license;

    private RemoteManifest? _lastManifest;
    private CancellationTokenSource? _activeDownloadCts;
    private VersionRowViewModel? _activeRow;

    public ObservableCollection<VersionRowViewModel> OtherVersions { get; } = new();

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasFeaturedVersion))]
    [NotifyPropertyChangedFor(nameof(HasOtherVersions))]
    [NotifyPropertyChangedFor(nameof(EmptyHintVisibility))]
    private VersionRowViewModel? _featuredVersion;

    public bool HasFeaturedVersion => FeaturedVersion is not null;
    public bool HasOtherVersions => OtherVersions.Count > 0;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(FooterText))]
    [NotifyPropertyChangedFor(nameof(FooterVisibility))]
    private string? _statusMessage;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(FooterText))]
    [NotifyPropertyChangedFor(nameof(FooterVisibility))]
    [NotifyCanExecuteChangedFor(nameof(CheckForUpdatesCommand))]
    private bool _isBusy;

    [ObservableProperty] private double _progressPercent;
    [ObservableProperty] private string? _progressDetail;

    public string LicenseSummary
    {
        get
        {
            if (_license is null) return "Aucune license";
            if (_license.Status == "valid")
            {
                var until = _license.DownloadEntitlementUntil;
                if (until is { } u && (u - DateTime.UtcNow).TotalDays < 30)
                    return $"{_license.OwnerName} • exp. {u:dd/MM/yyyy}";
                return $"{_license.OwnerName} • exp. {until:dd/MM/yyyy}";
            }
            if (_license.Status == "expired")
                return $"Expirée le {_license.DownloadEntitlementUntil:dd/MM/yyyy}";
            if (_license.Status == "revoked")
                return "Révoquée";
            return _license.Status;
        }
    }

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

    /// <summary>
    /// Catégorie utilisée par la UI pour piloter la couleur du badge license.
    /// </summary>
    public string LicenseSeverity
    {
        get
        {
            if (_license is null) return "Error";
            return _license.Status switch
            {
                "valid" when IsLicenseExpiringSoon() => "Warning",
                "valid"                              => "Ok",
                _                                    => "Error",
            };
        }
    }

    private bool IsLicenseExpiringSoon()
    {
        if (_license?.DownloadEntitlementUntil is not { } until) return false;
        return (until - DateTime.UtcNow).TotalDays < 30;
    }

    private void NotifyLicenseChanged()
    {
        OnPropertyChanged(nameof(LicenseSummary));
        OnPropertyChanged(nameof(LicenseIcon));
        OnPropertyChanged(nameof(LicenseSeverity));
    }

    public string EmptyHint =>
        "Aucune version locale ni distante.\n" +
        "Clique « Vérifier les mises à jour » pour récupérer le catalogue depuis le serveur, " +
        "ou place un dossier « Proserve v{X.Y.Z} » contenant PROSERVE_UE_5_5.exe dans :\n" +
        _config.InstallRoot;

    public Visibility EmptyHintVisibility =>
        FeaturedVersion is null && OtherVersions.Count == 0
            ? Visibility.Visible : Visibility.Collapsed;

    public Visibility FooterVisibility =>
        IsBusy || !string.IsNullOrEmpty(StatusMessage) ? Visibility.Visible : Visibility.Collapsed;

    public string FooterText =>
        !string.IsNullOrEmpty(ProgressDetail) ? ProgressDetail :
        StatusMessage ?? string.Empty;

    public MainViewModel(
        IInstallationRegistry registry,
        IProcessLauncher processLauncher,
        IConfigStore configStore,
        LocalConfig config,
        IManifestService manifestService,
        IUpdateChecker updateChecker,
        IDownloadManager downloadManager,
        IZipInstaller zipInstaller,
        ILicenseService licenseService,
        IToastService toastService,
        IServiceProvider serviceProvider,
        ILogger<MainViewModel> logger)
    {
        _registry = registry;
        _processLauncher = processLauncher;
        _configStore = configStore;
        _config = config;
        _manifestService = manifestService;
        _updateChecker = updateChecker;
        _downloadManager = downloadManager;
        _zipInstaller = zipInstaller;
        _licenseService = licenseService;
        _toastService = toastService;
        _serviceProvider = serviceProvider;
        _logger = logger;

        // Charge la license depuis le cache (pas d'appel réseau au démarrage,
        // ça reste rapide ; un refresh proactif arrive dès qu'on clique « Vérifier les MAJ »)
        _license = _licenseService.GetCached();

        RebuildList();

        // Vérification automatique des MAJ au démarrage (silencieuse et non bloquante)
        _ = Task.Run(async () =>
        {
            try
            {
                await Task.Delay(500); // laisse l'UI se stabiliser
                await Application.Current.Dispatcher.InvokeAsync(async () =>
                {
                    await CheckForUpdatesAsync();
                });
            }
            catch (Exception ex) { _logger.LogWarning(ex, "Auto check at startup failed"); }
        });
    }

    /// <summary>
    /// Recompose la liste affichée à partir du scan local + du dernier manifest distant.
    /// La version « courante » (FeaturedVersion) est la plus haute installée ; à défaut,
    /// la plus haute disponible côté serveur. Toutes les autres vont dans OtherVersions.
    /// </summary>
    private void RebuildList()
    {
        var installed = _registry.Scan().ToDictionary(v => v.Version);
        var remote = _lastManifest?.Versions ?? new List<VersionManifest>();
        var remoteByVer = remote.ToDictionary(v => v.Version);

        var allVersions = installed.Keys.Union(remoteByVer.Keys)
            .OrderByDescending(v => SemVer.Parse(v))
            .ToList();

        var rows = new List<VersionRowViewModel>();
        foreach (var ver in allVersions)
        {
            VersionRowViewModel row;
            if (installed.TryGetValue(ver, out var inst))
            {
                row = VersionRowViewModel.ForInstalled(inst, remoteByVer.GetValueOrDefault(ver));
            }
            else
            {
                row = VersionRowViewModel.ForRemote(remoteByVer[ver]);
            }
            WireRowHandlers(row);
            rows.Add(row);
        }

        // Marque les rows distantes qui ont un DL en pause (partial + state.json présents)
        // et applique le filtre license
        foreach (var r in rows.Where(r => r.IsRemoteOnly))
        {
            var st = _downloadManager.GetResumableState(r.Version);
            if (st is not null) r.ResumableBytes = st.DownloadedBytes;

            // License : la version est-elle téléchargeable selon notre entitlement ?
            r.LicenseAllowsDownload = r.Remote is not null
                && _licenseService.CanDownloadVersion(_license, r.Remote);
        }

        // Featured : plus haute installée, sinon plus haute distante
        var featured = rows.FirstOrDefault(r => r.IsInstalled) ?? rows.FirstOrDefault();
        FeaturedVersion = featured;

        OtherVersions.Clear();
        foreach (var r in rows)
        {
            if (!ReferenceEquals(r, featured))
                OtherVersions.Add(r);
        }

        OnPropertyChanged(nameof(EmptyHint));
        OnPropertyChanged(nameof(EmptyHintVisibility));
        OnPropertyChanged(nameof(HasFeaturedVersion));
        OnPropertyChanged(nameof(HasOtherVersions));
    }

    private void WireRowHandlers(VersionRowViewModel row)
    {
        row.LaunchHandler = LaunchVersion;
        row.InstallHandler = r => _ = InstallVersionAsync(r);
        row.UninstallHandler = r => _ = UninstallVersionAsync(r);
        row.ShowReleaseNotesHandler = r => _ = ShowReleaseNotesAsync(r);
        row.OpenFolderHandler = OpenVersionFolder;
    }

    [RelayCommand(CanExecute = nameof(CanCheckUpdates))]
    private async Task CheckForUpdatesAsync()
    {
        IsBusy = true;
        StatusMessage = "Vérification des mises à jour…";
        try
        {
            var result = await _updateChecker.CheckAsync(CancellationToken.None);
            if (result.Error is not null)
            {
                StatusMessage = $"Erreur : {result.Error}";
                return;
            }

            _lastManifest = result.Manifest;
            RebuildList();

            if (result.IsLatestNewerThanInstalled && result.LatestRemote is not null)
                StatusMessage = $"Nouvelle version disponible : v{result.LatestRemote.Version}";
            else if (result.LatestRemote is not null)
                StatusMessage = $"À jour (v{result.LatestRemote.Version})";
            else
                StatusMessage = "Aucune version distante trouvée";
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Update check failed");
            StatusMessage = $"Erreur : {ex.Message}";
        }
        finally
        {
            IsBusy = false;
        }
    }
    private bool CanCheckUpdates() => !IsBusy;

    [RelayCommand]
    private void OpenSettings()
    {
        var vm = _serviceProvider.GetRequiredService<SettingsViewModel>();
        var dialog = new Views.SettingsDialog(vm) { Owner = Application.Current.MainWindow };
        if (dialog.ShowDialog() == true)
        {
            // Au retour, l'URL serveur ou l'installRoot ont pu changer → refresh
            _license = _licenseService.GetCached();
            NotifyLicenseChanged();
            RebuildList();
        }
    }

    [RelayCommand]
    private async Task ActivateLicenseAsync()
    {
        var dialog = new Views.OnboardingDialog(_licenseService) { Owner = Application.Current.MainWindow };
        dialog.ShowDialog();
        if (dialog.LicenseActivated)
        {
            _license = _licenseService.GetCached();
            NotifyLicenseChanged();
            RebuildList();
        }
        await Task.CompletedTask;
    }

    [RelayCommand]
    private void OpenInstallRoot()
    {
        if (!Directory.Exists(_config.InstallRoot)) return;
        Process.Start(new ProcessStartInfo
        {
            FileName = _config.InstallRoot,
            UseShellExecute = true,
            Verb = "open"
        });
    }

    [RelayCommand]
    private void CancelDownload() => _activeDownloadCts?.Cancel();

    // ------------ Per-row actions ------------

    private void LaunchVersion(VersionRowViewModel row)
    {
        if (row.Installed is null) return;
        try
        {
            _processLauncher.Launch(row.Installed);
            _config.LastLaunchedVersion = row.Version;
            _configStore.Save(_config);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Launch failed");
            MessageBox.Show($"Impossible de lancer la version :\n\n{ex.Message}",
                "Erreur de lancement", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }

    private async Task InstallVersionAsync(VersionRowViewModel row)
    {
        if (row.Remote is null) return;
        if (IsBusy) { MessageBox.Show("Une autre opération est déjà en cours.", "Patience", MessageBoxButton.OK, MessageBoxImage.Information); return; }

        IsBusy = true;
        _activeRow = row;
        _activeDownloadCts = new CancellationTokenSource();
        var ct = _activeDownloadCts.Token;

        try
        {
            // Si reprise d'un DL interrompu, on saute la popup de release notes :
            // l'utilisateur a déjà confirmé sa décision la première fois.
            var isResume = row.HasResumableDownload;

            if (!isResume)
            {
                // 1) Récupère release notes (best effort)
                string notes = "_Aucune release note fournie._";
                if (!string.IsNullOrEmpty(row.Remote.ReleaseNotesUrl))
                {
                    try
                    {
                        notes = await _manifestService.FetchReleaseNotesAsync(row.Remote.ReleaseNotesUrl, ct);
                    }
                    catch (Exception ex) { _logger.LogWarning(ex, "Release notes fetch failed"); notes = "_Release notes indisponibles._"; }
                }

                // 2) Dialog de confirmation avec release notes
                var dialog = new UpdateAvailableDialog(row.Remote, notes) { Owner = Application.Current.MainWindow };
                dialog.ShowDialog();
                if (!dialog.DownloadRequested) return;
            }

            // 3) Download
            row.State = VersionRowState.Downloading;
            var url = new Uri(row.Remote.Download.Url);
            var job = new DownloadJob(row.Version, url, row.Remote.Download.SizeBytes, row.Remote.Download.Sha256);
            var dlProgress = new Progress<DownloadProgress>(p => UpdateDlProgress(row, p));
            StatusMessage = $"Téléchargement v{row.Version}…";
            var zipPath = await _downloadManager.DownloadAsync(job, dlProgress, ct);

            // 4) Install
            row.State = VersionRowState.Installing;
            StatusMessage = $"Installation v{row.Version}…";
            var folderName = row.Remote.GetInstallFolderName();
            var target = Path.Combine(_config.InstallRoot, folderName);
            var installProgress = new Progress<InstallProgress>(ip =>
            {
                if (ip.BytesTotal <= 0) return;
                var pct = (double)ip.BytesDone / ip.BytesTotal * 100.0;
                ProgressPercent = pct;
                row.ProgressPercent = pct;
                ProgressDetail = $"Extraction v{row.Version} : {ip.EntriesDone}/{ip.EntriesTotal} fichiers ({FormatSize(ip.BytesDone)} / {FormatSize(ip.BytesTotal)})";
                row.ProgressDetail = ProgressDetail;
            });
            await _zipInstaller.InstallAsync(zipPath, target, installProgress, ct);

            try { File.Delete(zipPath); } catch { /* non critique */ }

            StatusMessage = $"v{row.Version} installée avec succès";
            ProgressDetail = null;
            ProgressPercent = 0;
            RebuildList();
            _toastService.ShowSuccess(
                "Installation terminée",
                $"Proserve v{row.Version} est prête à être lancée.");
        }
        catch (OperationCanceledException)
        {
            StatusMessage = "Téléchargement annulé";
            row.State = VersionRowState.AvailableIdle;
            row.ProgressDetail = null;
            row.ProgressPercent = 0;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Install failed for {Version}", row.Version);
            row.State = VersionRowState.AvailableIdle;
            row.ProgressDetail = null;
            row.ProgressPercent = 0;
            MessageBox.Show(
                $"Le téléchargement ou l'installation a échoué :\n\n{ex.Message}",
                "Erreur", MessageBoxButton.OK, MessageBoxImage.Error);
            StatusMessage = $"Erreur : {ex.Message}";
            _toastService.ShowError($"Échec v{row.Version}", ex.Message);
        }
        finally
        {
            _activeDownloadCts?.Dispose();
            _activeDownloadCts = null;
            _activeRow = null;
            IsBusy = false;
        }
    }

    private async Task UninstallVersionAsync(VersionRowViewModel row)
    {
        if (row.Installed is null) return;

        var sizeStr = FormatSize(row.SizeBytes);
        var confirm = MessageBox.Show(
            $"Supprimer la version v{row.Version} ?\n\nDossier : {row.FolderPath}\nLibérera {sizeStr}.\n\nCette action est irréversible.",
            "Confirmer la suppression",
            MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);

        if (confirm != MessageBoxResult.Yes) return;

        if (IsBusy) { MessageBox.Show("Une autre opération est déjà en cours.", "Patience", MessageBoxButton.OK, MessageBoxImage.Information); return; }

        IsBusy = true;
        row.State = VersionRowState.Uninstalling;
        StatusMessage = $"Suppression v{row.Version}…";
        try
        {
            await _registry.DeleteAsync(row.Version, null, CancellationToken.None);
            StatusMessage = $"v{row.Version} supprimée";
            RebuildList();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Uninstall failed for {Version}", row.Version);
            row.State = VersionRowState.InstalledIdle;
            MessageBox.Show($"Suppression échouée : {ex.Message}", "Erreur",
                MessageBoxButton.OK, MessageBoxImage.Error);
            StatusMessage = $"Erreur : {ex.Message}";
        }
        finally
        {
            IsBusy = false;
        }
    }

    private async Task ShowReleaseNotesAsync(VersionRowViewModel row)
    {
        var url = row.Remote?.ReleaseNotesUrl;
        if (string.IsNullOrEmpty(url))
        {
            MessageBox.Show("Aucune release note disponible pour cette version.",
                "Release notes", MessageBoxButton.OK, MessageBoxImage.Information);
            return;
        }
        string notes;
        try { notes = await _manifestService.FetchReleaseNotesAsync(url, CancellationToken.None); }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to fetch release notes");
            MessageBox.Show($"Impossible de récupérer les release notes :\n{ex.Message}",
                "Erreur", MessageBoxButton.OK, MessageBoxImage.Warning);
            return;
        }

        var dlg = new ReleaseNotesViewerDialog(row.Version, notes) { Owner = Application.Current.MainWindow };
        dlg.ShowDialog();
    }

    private void OpenVersionFolder(VersionRowViewModel row)
    {
        if (string.IsNullOrEmpty(row.FolderPath) || !Directory.Exists(row.FolderPath)) return;
        Process.Start(new ProcessStartInfo
        {
            FileName = row.FolderPath,
            UseShellExecute = true,
            Verb = "open"
        });
    }

    private void UpdateDlProgress(VersionRowViewModel row, DownloadProgress p)
    {
        if (p.TotalBytes > 0)
        {
            var pct = (double)p.BytesDownloaded / p.TotalBytes * 100.0;
            ProgressPercent = pct;
            row.ProgressPercent = pct;
        }

        var sb = new System.Text.StringBuilder();
        sb.Append("⬇ v").Append(row.Version).Append(" : ");
        sb.Append(FormatSize(p.BytesDownloaded));
        if (p.TotalBytes > 0) sb.Append(" / ").Append(FormatSize(p.TotalBytes));
        if (p.BytesPerSecond > 0) sb.Append(" • ").Append(FormatSize((long)p.BytesPerSecond)).Append("/s");
        if (p.Eta is { } eta && eta.TotalSeconds > 1) sb.Append(" • ETA ").Append(FormatEta(eta));
        var s = sb.ToString();
        ProgressDetail = s;
        row.ProgressDetail = s;
    }

    private static string FormatSize(long bytes)
    {
        if (bytes <= 0) return "0 o";
        string[] units = { "o", "Ko", "Mo", "Go", "To" };
        double v = bytes;
        int u = 0;
        while (v >= 1024 && u < units.Length - 1) { v /= 1024; u++; }
        return $"{v:0.#} {units[u]}";
    }

    private static string FormatEta(TimeSpan eta)
    {
        if (eta.TotalHours >= 1) return $"{(int)eta.TotalHours}h{eta.Minutes:D2}";
        if (eta.TotalMinutes >= 1) return $"{(int)eta.TotalMinutes}m{eta.Seconds:D2}";
        return $"{(int)eta.TotalSeconds}s";
    }
}
