using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using PSLauncher.Core.Localization;
using PSLauncher.Models;

namespace PSLauncher.App.ViewModels;

public enum VersionRowState
{
    InstalledIdle,        // Installée localement, pas d'action en cours
    AvailableIdle,        // Disponible côté serveur, pas installée
    Downloading,
    Verifying,            // SHA-256 du ZIP téléchargé en cours
    Installing,
    Uninstalling,
}

public sealed partial class VersionRowViewModel : ObservableObject
{
    public string Version { get; }
    public DateTime ReleasedAt { get; }
    public string ExecutablePath { get; }
    public string FolderPath { get; }
    public long SizeBytes { get; }
    public InstalledVersion? Installed { get; }
    public VersionManifest? Remote { get; }
    public bool HasReleaseNotes => Remote?.ReleaseNotesUrl is not null;

    /// <summary>
    /// True si la version est taggée BÊTA dans le manifest (champ
    /// <see cref="VersionManifest.IsBeta"/>). Pilote l'affichage du badge
    /// orange "BÊTA" + tooltip avec <see cref="BetaNotes"/>.
    /// </summary>
    public bool IsBeta => Remote?.IsBeta ?? false;

    /// <summary>
    /// Note des testeurs (texte libre). Affichée en tooltip quand on hover
    /// le badge BÊTA. Null si non renseigné côté serveur.
    /// </summary>
    public string? BetaNotes => Remote?.BetaNotes;

    /// <summary>Tooltip composé pour le hover sur le badge BÊTA.</summary>
    public string BetaTooltip => string.IsNullOrEmpty(BetaNotes)
        ? Strings.BetaBadgeTooltipDefault
        : Strings.BetaBadgeTooltip(BetaNotes!);

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(StateLabel))]
    [NotifyPropertyChangedFor(nameof(IsBusy))]
    [NotifyPropertyChangedFor(nameof(IsInstalled))]
    [NotifyPropertyChangedFor(nameof(IsRemoteOnly))]
    [NotifyPropertyChangedFor(nameof(ShowRestartFromZero))]
    [NotifyCanExecuteChangedFor(nameof(LaunchCommand))]
    [NotifyCanExecuteChangedFor(nameof(InstallCommand))]
    [NotifyCanExecuteChangedFor(nameof(UninstallCommand))]
    private VersionRowState _state;

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

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(InstallButtonLabel))]
    [NotifyPropertyChangedFor(nameof(HasResumableDownload))]
    [NotifyPropertyChangedFor(nameof(ShowRestartFromZero))]
    [NotifyCanExecuteChangedFor(nameof(RestartFromZeroCommand))]
    private long _resumableBytes;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(InstallButtonLabel))]
    [NotifyPropertyChangedFor(nameof(LicenseAllowsInstall))]
    [NotifyCanExecuteChangedFor(nameof(InstallCommand))]
    private bool _licenseAllowsDownload = true;

    /// <summary>
    /// True quand cette version est la version désignée pour l'auto-launch
    /// (bouton AUTO orange/coloré). Une seule row peut être sélectionnée à la
    /// fois ; le MainViewModel maintient l'unicité quand on toggle.
    /// </summary>
    [ObservableProperty]
    private bool _isAutoModeSelected;

    /// <summary>
    /// True si le bouton AUTO doit être affiché sur cette row (master toggle ON
    /// + version installée). Piloté par le MainViewModel via property update à
    /// chaque RebuildList.
    /// </summary>
    [ObservableProperty]
    private bool _showAutoButton;

    public Action<VersionRowViewModel>? ToggleAutoModeHandler { get; set; }

    [RelayCommand]
    private void ToggleAutoMode()
    {
        // Trace fichier pour diagnostiquer "le bouton AUTO ne fait rien".
        // Si on voit cette ligne mais pas la suivante côté MainViewModel,
        // c'est que le handler n'est pas attaché (bug WireRowHandlers).
        try
        {
            var dir = System.IO.Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                "PSLauncher", "logs");
            System.IO.Directory.CreateDirectory(dir);
            System.IO.File.AppendAllText(
                System.IO.Path.Combine(dir, "auto-mode.log"),
                $"{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss.fff}Z  VersionRowViewModel.ToggleAutoMode invoked for v{Version} (handler attached={ToggleAutoModeHandler is not null}){Environment.NewLine}");
        }
        catch { }
        ToggleAutoModeHandler?.Invoke(this);
    }

    public bool HasResumableDownload => ResumableBytes > 0;
    public bool LicenseAllowsInstall => LicenseAllowsDownload;

    /// <summary>
    /// Visibilité du bouton rouge « Annuler » à côté de « ↻ Reprendre ».
    /// Doit être vrai uniquement quand un partial existe ET qu'aucun DL n'est en
    /// cours sur cette row (sinon le bouton « Annuler » se mélange visuellement
    /// avec la barre de progression du DL actif, qui a son propre cancel).
    /// </summary>
    public bool ShowRestartFromZero => HasResumableDownload && State == VersionRowState.AvailableIdle;

    public string InstallButtonLabel
    {
        get
        {
            if (!LicenseAllowsDownload) return Strings.ActionLicenseInsufficient;
            if (!HasResumableDownload) return Strings.ActionInstall;
            if (Remote is null || Remote.Download.SizeBytes <= 0) return Strings.ResumeButtonNoPercent;
            var pct = (int)Math.Round((double)ResumableBytes / Remote.Download.SizeBytes * 100.0);
            return Strings.ResumeButton(pct);
        }
    }

    public bool IsInstalled => State is VersionRowState.InstalledIdle or VersionRowState.Uninstalling;
    public bool IsRemoteOnly => State is VersionRowState.AvailableIdle;
    public bool IsBusy => State is VersionRowState.Downloading
                                or VersionRowState.Verifying
                                or VersionRowState.Installing
                                or VersionRowState.Uninstalling;

    public string StateLabel => State switch
    {
        VersionRowState.InstalledIdle => Strings.StatusInstalled,
        VersionRowState.AvailableIdle => Strings.StatusAvailable,
        VersionRowState.Downloading   => Strings.StatusDownloading,
        VersionRowState.Verifying     => Strings.StatusVerifying,
        VersionRowState.Installing    => Strings.StatusInstalling,
        VersionRowState.Uninstalling  => Strings.StatusUninstalling,
        _ => string.Empty
    };

    public string PrimaryDate => ReleasedAt > DateTime.MinValue
        ? Strings.FormatDate(ReleasedAt)
        : "—";

    public string SizeDisplay => FormatSize(SizeBytes);

    /// <summary>Constructeur ligne « installée localement » (peut aussi être présente en remote).</summary>
    public static VersionRowViewModel ForInstalled(InstalledVersion installed, VersionManifest? remote)
    {
        return new VersionRowViewModel(
            version: installed.Version,
            releasedAt: remote?.ReleasedAt ?? installed.InstalledAt,
            executablePath: installed.ExecutablePath,
            folderPath: installed.FolderPath,
            sizeBytes: installed.SizeBytes,
            installed: installed,
            remote: remote,
            initialState: VersionRowState.InstalledIdle);
    }

    /// <summary>Constructeur ligne « disponible côté serveur uniquement ».</summary>
    public static VersionRowViewModel ForRemote(VersionManifest remote)
    {
        return new VersionRowViewModel(
            version: remote.Version,
            releasedAt: remote.ReleasedAt,
            executablePath: string.Empty,
            folderPath: string.Empty,
            sizeBytes: remote.Download.SizeBytes,
            installed: null,
            remote: remote,
            initialState: VersionRowState.AvailableIdle);
    }

    private VersionRowViewModel(
        string version, DateTime releasedAt, string executablePath, string folderPath,
        long sizeBytes, InstalledVersion? installed, VersionManifest? remote,
        VersionRowState initialState)
    {
        Version = version;
        ReleasedAt = releasedAt;
        ExecutablePath = executablePath;
        FolderPath = folderPath;
        SizeBytes = sizeBytes;
        Installed = installed;
        Remote = remote;
        _state = initialState;
    }

    // Les commandes sont câblées par le MainViewModel après instanciation
    // (les actions ont besoin de services).

    public Action<VersionRowViewModel>? LaunchHandler { get; set; }
    public Action<VersionRowViewModel>? InstallHandler { get; set; }
    public Action<VersionRowViewModel>? UninstallHandler { get; set; }
    public Action<VersionRowViewModel>? ShowReleaseNotesHandler { get; set; }
    public Action<VersionRowViewModel>? OpenFolderHandler { get; set; }
    public Action<VersionRowViewModel>? RestartFromZeroHandler { get; set; }
    public Action<VersionRowViewModel>? ForceFreshDownloadHandler { get; set; }

    [RelayCommand(CanExecute = nameof(CanLaunch))]
    private void Launch() => LaunchHandler?.Invoke(this);
    private bool CanLaunch() => State == VersionRowState.InstalledIdle;

    [RelayCommand(CanExecute = nameof(CanInstall))]
    private void Install() => InstallHandler?.Invoke(this);
    private bool CanInstall() => State == VersionRowState.AvailableIdle && LicenseAllowsDownload;

    [RelayCommand(CanExecute = nameof(CanUninstall))]
    private void Uninstall() => UninstallHandler?.Invoke(this);
    private bool CanUninstall() => State == VersionRowState.InstalledIdle;

    [RelayCommand]
    private void ShowReleaseNotes() => ShowReleaseNotesHandler?.Invoke(this);

    [RelayCommand]
    private void OpenFolder() => OpenFolderHandler?.Invoke(this);

    [RelayCommand(CanExecute = nameof(CanRestartFromZero))]
    private void RestartFromZero() => RestartFromZeroHandler?.Invoke(this);
    private bool CanRestartFromZero() => HasResumableDownload;

    /// <summary>
    /// Visible quand la row n'est pas en cours de DL (sinon le user devrait
    /// d'abord cancel via le bouton dédié). Pas conditionnée par "partial existe"
    /// car cette action a pour but spécifique de purger TOUT (cache + partial +
    /// sidecar SHA), même quand il n'y a pas de partial en cours.
    /// </summary>
    [RelayCommand]
    private void ForceFreshDownload() => ForceFreshDownloadHandler?.Invoke(this);

    private static string FormatSize(long bytes)
        => bytes <= 0 ? "—" : Strings.FormatSize(bytes);
}
