using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
using NexusShell.Models;
using NexusShell.Services;

namespace NexusShell.ViewModels;

public sealed class UpdateInfoViewModel : INotifyPropertyChanged
{
    private readonly UpdateService _updateService;
    private bool _isChecking;
    private string? _progressLabel;
    private string? _statusLabelOverride;

    public UpdateInfoViewModel(UpdateService updateService)
    {
        _updateService = updateService;
        RefreshCommand = new RelayCommand(async _ => await RefreshAsync(), _ => !IsBusy);
        DownloadAndInstallCommand = new RelayCommand(async _ => await DownloadAndInstallAsync(), _ => CanDownloadAndInstall);
        DownloadCommand = new RelayCommand(async _ => await DownloadAsync(), _ => CanDownload);
        InstallCommand = new RelayCommand(async _ => await InstallAsync(), _ => CanInstall);
        RetryCommand = new RelayCommand(async _ => await RetryInstallAsync(), _ => CanRetry);
        CancelCommand = new RelayCommand(_ => CancelDownload(), _ => CanCancel);
        ClearStuckCommand = new RelayCommand(_ => ClearStuck(), _ => CanClearStuck);
        OpenLogsCommand = new RelayCommand(_ => OpenUpdateLogs());
        CloseCommand = new RelayCommand(_ => CloseRequested?.Invoke(this, EventArgs.Empty));
    }

    public event PropertyChangedEventHandler? PropertyChanged;
    public event EventHandler? CloseRequested;

    public string CurrentVersion => _updateService.CurrentVersion;
    public string CurrentBuild => _updateService.CurrentBuild.ToString();
    public string ManifestUrl => _updateService.ManifestUrl;
    public string Channel => _updateService.LastResult?.RemoteManifest?.Channel ?? "poc";

    public string StatusLabel => _statusLabelOverride ?? (_updateService.FlowState switch
    {
        ClientUpdateFlowState.Downloading => "Downloader opdatering…",
        ClientUpdateFlowState.Validating => "Verificerer SHA256…",
        ClientUpdateFlowState.Ready => "Opdatering klar til installation",
        ClientUpdateFlowState.Preparing => "Afventer — forbereder installation…",
        ClientUpdateFlowState.Installing => "Installerer opdatering…",
        ClientUpdateFlowState.Blocked =>
            _updateService.LastResult?.ErrorMessage ?? "Opdatering blokeret — filer er låst",
        ClientUpdateFlowState.Failed => _updateService.LastResult?.ErrorMessage ?? "Opdatering fejlede",
        ClientUpdateFlowState.Stale =>
            _updateService.LastResult?.ErrorMessage ?? "Opdatering hang — timeout. Ryd fastlåst og prøv igen.",
        ClientUpdateFlowState.Cancelled => "Download annulleret",
        ClientUpdateFlowState.UpToDate when IsLocalBuildAhead =>
            $"Du kører en nyere lokal build end update-kanalen — lokal build {CurrentBuild}, CDN latest build {LatestBuild}",
        ClientUpdateFlowState.UpToDate => "Opdateringer: Ajour",
        ClientUpdateFlowState.UpdateFound => $"Opdatering tilgængelig: {LatestVersion} (build {LatestBuild})",
        _ => _updateService.LastResult?.Status switch
        {
            UpdateCheckStatus.UpToDate => "Opdateringer: Ajour",
            UpdateCheckStatus.UpdateAvailable =>
                $"Opdatering tilgængelig: {LatestVersion} (build {LatestBuild})",
            UpdateCheckStatus.LocalBuildAhead =>
                $"Du kører en nyere lokal build end update-kanalen — lokal build {CurrentBuild}, CDN latest build {LatestBuild}",
            UpdateCheckStatus.ChannelUnavailable => "Opdateringskanal utilgængelig",
            UpdateCheckStatus.Checking => "Søger efter opdateringer…",
            UpdateCheckStatus.ClientTooOld => "Klient-build for gammel til denne opdatering",
            UpdateCheckStatus.Error => "Opdateringskanal utilgængelig",
            _ => "Opdateringsstatus ukendt",
        },
    });

    public string ProgressLabel => _progressLabel ?? string.Empty;
    public bool HasProgress => !string.IsNullOrWhiteSpace(_progressLabel);

    public string LatestVersion => _updateService.LastResult?.RemoteManifest?.Version ?? "—";
    public string LatestBuild => _updateService.LastResult?.RemoteManifest?.Build.ToString() ?? "—";
    public bool IsUpdateAvailable => _updateService.LastResult?.IsUpdateAvailable == true;
    public bool IsLocalBuildAhead => _updateService.LastResult?.Status == UpdateCheckStatus.LocalBuildAhead;

    public string PackageSizeLabel
    {
        get
        {
            var bytes = _updateService.LastResult?.RemoteManifest?.EffectiveSizeBytes ?? 0;
            return bytes <= 0 ? "—" : FormatBytes(bytes);
        }
    }

    public string Sha256Preview
    {
        get
        {
            var hash = _updateService.LastResult?.RemoteManifest?.EffectiveSha256;
            if (string.IsNullOrWhiteSpace(hash) || hash.Length < 16)
            {
                return "—";
            }

            return $"{hash[..16]}…";
        }
    }

    public string InstallNotice => _updateService.FlowState switch
    {
        ClientUpdateFlowState.Blocked =>
            "Identity eller Shell kører stadig og låser filer (fx e_sqlite3.dll). Luk Nexus, eller brug Prøv igen.",
        ClientUpdateFlowState.Failed =>
            "Installation fejlede. Se update.log under %LocalAppData%\\NinjaNexus\\logs og prøv igen.",
        _ when IsLocalBuildAhead =>
            "Ajour: Denne lokale/dev build er nyere end CDN latest. Downgrade er blokeret.",
        _ =>
            "POC: Download og SHA256 kræver bekræftelse. Shell og Identity stoppes før filer overskrives.",
    };

    public string LogsPath => Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        "NinjaNexus",
        "logs",
        "update.log");

    public ObservableCollection<string> ReleaseNotes { get; } = [];

    public bool IsChecking
    {
        get => _isChecking;
        private set
        {
            _isChecking = value;
            OnPropertyChanged();
            OnPropertyChanged(nameof(IsBusy));
            OnPropertyChanged(nameof(IsNotBusy));
        }
    }

    public bool IsBusy => IsChecking
        || _updateService.FlowState is ClientUpdateFlowState.Downloading
            or ClientUpdateFlowState.Validating
            or ClientUpdateFlowState.Preparing
            or ClientUpdateFlowState.Installing;

    public bool IsNotBusy => !IsBusy;

    public bool CanDownload => IsUpdateAvailable && !IsBusy
        && _updateService.FlowState is ClientUpdateFlowState.UpdateFound
            or ClientUpdateFlowState.Failed
            or ClientUpdateFlowState.Stale
            or ClientUpdateFlowState.Cancelled
            or ClientUpdateFlowState.Idle;

    public bool CanDownloadAndInstall => CanDownload;

    public bool CanInstall => _updateService.FlowState == ClientUpdateFlowState.Ready && !IsBusy;

    public bool CanRetry => !IsBusy
        && _updateService.FlowState is ClientUpdateFlowState.Failed
            or ClientUpdateFlowState.Blocked
            or ClientUpdateFlowState.Stale
        && _updateService.StagedZipPath is not null;

    public bool CanClearStuck => !IsChecking
        && _updateService.FlowState is ClientUpdateFlowState.Failed
            or ClientUpdateFlowState.Blocked
            or ClientUpdateFlowState.Stale
            or ClientUpdateFlowState.Preparing
            or ClientUpdateFlowState.Installing;

    public bool CanCancel => _updateService.FlowState is ClientUpdateFlowState.Downloading
        or ClientUpdateFlowState.Validating;

    public bool CanApplyUpdate => CanInstall;

    public ICommand RefreshCommand { get; }
    public ICommand DownloadAndInstallCommand { get; }
    public ICommand DownloadCommand { get; }
    public ICommand InstallCommand { get; }
    public ICommand RetryCommand { get; }
    public ICommand CancelCommand { get; }
    public ICommand ClearStuckCommand { get; }
    public ICommand OpenLogsCommand { get; }
    public ICommand ApplyUpdateCommand => InstallCommand;
    public ICommand CloseCommand { get; }

    public async Task RefreshAsync()
    {
        IsChecking = true;
        _progressLabel = null;
        try
        {
            await _updateService.CheckForUpdatesAsync();
            LoadNotes();
            _statusLabelOverride = null;
            NotifyAll();
        }
        finally
        {
            IsChecking = false;
            NotifyCommandStates();
        }
    }

    public async Task DownloadAsync()
    {
        if (!IsUpdateAvailable || _updateService.LastResult?.RemoteManifest is null)
        {
            return;
        }

        IsChecking = true;
        var progress = new Progress<string>(s =>
        {
            _progressLabel = s;
            OnPropertyChanged(nameof(ProgressLabel));
            OnPropertyChanged(nameof(HasProgress));
            OnPropertyChanged(nameof(StatusLabel));
        });

        try
        {
            var result = await _updateService.DownloadUpdateAsync(
                _updateService.LastResult.RemoteManifest, progress);
            if (result.Status == UpdateApplyStatus.Failed)
            {
                _statusLabelOverride = result.ErrorMessage ?? "Download failed";
            }
            else
            {
                _statusLabelOverride = null;
            }

            NotifyAll();
        }
        finally
        {
            IsChecking = false;
            _progressLabel = null;
            OnPropertyChanged(nameof(ProgressLabel));
            OnPropertyChanged(nameof(HasProgress));
            NotifyCommandStates();
        }
    }

    public async Task ApplyUpdateAsync() => await InstallAsync();

    public async Task DownloadAndInstallAsync()
    {
        if (!CanDownloadAndInstall)
        {
            return;
        }

        await DownloadAsync();
        if (_updateService.FlowState == ClientUpdateFlowState.Ready)
        {
            await InstallAsync();
        }
    }

    public async Task InstallAsync()
    {
        if (_updateService.FlowState != ClientUpdateFlowState.Ready)
        {
            return;
        }

        var manifest = _updateService.LastResult?.RemoteManifest;
        var confirm = MessageBox.Show(
            $"Installér Nexus build {manifest?.Build} ({manifest?.Version})?\n\nShell genstarter efter installation.",
            "Bekræft opdatering",
            MessageBoxButton.YesNo,
            MessageBoxImage.Question);
        if (confirm != MessageBoxResult.Yes)
        {
            return;
        }

        IsChecking = true;
        var progress = new Progress<string>(s =>
        {
            _progressLabel = s;
            OnPropertyChanged(nameof(ProgressLabel));
            OnPropertyChanged(nameof(HasProgress));
            OnPropertyChanged(nameof(StatusLabel));
        });

        try
        {
            var result = await _updateService.InstallStagedUpdateAsync(progress);
            if (result.RestartScheduled)
            {
                Application.Current.Shutdown();
                return;
            }

            if (result.Status is UpdateApplyStatus.Failed or UpdateApplyStatus.Blocked)
            {
                _statusLabelOverride = result.ErrorMessage
                    ?? (result.Status == UpdateApplyStatus.Blocked
                        ? "Opdatering blokeret — filer er låst"
                        : "Opdatering fejlede");
            }

            NotifyAll();
        }
        finally
        {
            IsChecking = false;
            _progressLabel = null;
            OnPropertyChanged(nameof(ProgressLabel));
            OnPropertyChanged(nameof(HasProgress));
            NotifyCommandStates();
        }
    }

    public async Task RetryInstallAsync()
    {
        if (!CanRetry)
        {
            return;
        }

        IsChecking = true;
        _statusLabelOverride = null;
        var progress = new Progress<string>(s =>
        {
            _progressLabel = s;
            OnPropertyChanged(nameof(ProgressLabel));
            OnPropertyChanged(nameof(HasProgress));
            OnPropertyChanged(nameof(StatusLabel));
        });

        try
        {
            var result = await _updateService.RetryInstallAsync(progress);
            if (result.RestartScheduled)
            {
                Application.Current.Shutdown();
                return;
            }

            if (result.Status is UpdateApplyStatus.Failed or UpdateApplyStatus.Blocked)
            {
                _statusLabelOverride = result.ErrorMessage;
            }

            NotifyAll();
        }
        finally
        {
            IsChecking = false;
            _progressLabel = null;
            OnPropertyChanged(nameof(ProgressLabel));
            OnPropertyChanged(nameof(HasProgress));
            NotifyCommandStates();
        }
    }

    private static void OpenUpdateLogs()
    {
        var logsDir = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "NinjaNexus",
            "logs");
        Directory.CreateDirectory(logsDir);
        try
        {
            Process.Start(new ProcessStartInfo
            {
                FileName = logsDir,
                UseShellExecute = true,
            });
        }
        catch
        {
            MessageBox.Show(
                $"Log-mappe: {logsDir}",
                "Nexus Updates",
                MessageBoxButton.OK,
                MessageBoxImage.Information);
        }
    }

    private void CancelDownload()
    {
        _updateService.CancelDownload();
        _progressLabel = null;
        _statusLabelOverride = "Download annulleret";
        OnPropertyChanged(nameof(ProgressLabel));
        OnPropertyChanged(nameof(HasProgress));
        NotifyAll();
        NotifyCommandStates();
    }

    private void ClearStuck()
    {
        _updateService.ClearStuckJob();
        _progressLabel = null;
        _statusLabelOverride = null;
        OnPropertyChanged(nameof(ProgressLabel));
        OnPropertyChanged(nameof(HasProgress));
        NotifyAll();
        NotifyCommandStates();
    }

    public void LoadFromCache()
    {
        LoadNotes();
        NotifyAll();
        NotifyCommandStates();
    }

    private void LoadNotes()
    {
        ReleaseNotes.Clear();
        var notes = _updateService.LastResult?.RemoteManifest?.EffectiveNotes ?? [];
        foreach (var note in notes)
        {
            ReleaseNotes.Add(note);
        }

        if (ReleaseNotes.Count == 0)
        {
            ReleaseNotes.Add("No release notes.");
        }
    }

    private void NotifyCommandStates()
    {
        OnPropertyChanged(nameof(CanDownload));
        OnPropertyChanged(nameof(CanDownloadAndInstall));
        OnPropertyChanged(nameof(CanInstall));
        OnPropertyChanged(nameof(CanRetry));
        OnPropertyChanged(nameof(CanCancel));
        OnPropertyChanged(nameof(CanClearStuck));
        OnPropertyChanged(nameof(CanApplyUpdate));
        OnPropertyChanged(nameof(Channel));
        OnPropertyChanged(nameof(IsBusy));
        OnPropertyChanged(nameof(IsNotBusy));
        CommandManager.InvalidateRequerySuggested();
    }

    private void NotifyAll()
    {
        OnPropertyChanged(nameof(StatusLabel));
        OnPropertyChanged(nameof(LatestVersion));
        OnPropertyChanged(nameof(LatestBuild));
        OnPropertyChanged(nameof(IsUpdateAvailable));
        OnPropertyChanged(nameof(IsLocalBuildAhead));
        OnPropertyChanged(nameof(PackageSizeLabel));
        OnPropertyChanged(nameof(Sha256Preview));
        OnPropertyChanged(nameof(CurrentVersion));
        OnPropertyChanged(nameof(CurrentBuild));
        OnPropertyChanged(nameof(InstallNotice));
        NotifyCommandStates();
    }

    private static string FormatBytes(long bytes)
    {
        if (bytes < 1024 * 1024)
        {
            return $"{bytes / 1024.0:F1} KB";
        }

        return $"{bytes / (1024.0 * 1024.0):F1} MB";
    }

    private void OnPropertyChanged([CallerMemberName] string? name = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
