using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Media;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Metin2Launcher.Config;
using Metin2Launcher.GameLaunch;
using Metin2Launcher.Localization;
using Metin2Launcher.Logging;
using Metin2Launcher.Manifest;
using Metin2Launcher.Orchestration;
using Metin2Launcher.Runtime;
using Velopack;
using Velopack.Sources;

namespace Metin2Launcher.UI.ViewModels;

public sealed partial class MainWindowViewModel : ObservableObject
{
    private readonly string _clientRoot;
    private readonly string _stateDir;
    private readonly string _settingsPath;
    private readonly LauncherSettings _settings;
    private readonly HttpClient _http;
    private MainWindow? _window;

    // Throttling
    private UpdateProgress? _pendingProgress;
    private readonly object _progressLock = new();
    private DispatcherTimer? _flushTimer;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(IsNewsEmpty), nameof(IsNewsPresent))]
    private string _statusText = "";
    [ObservableProperty] private string _detailText = "";

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(IsNewsEmpty), nameof(IsNewsPresent))]
    private string _newsText = "";

    [ObservableProperty] private double _progressValue;
    [ObservableProperty] private bool _isIndeterminate;
    [ObservableProperty] private bool _canPlay;
    [ObservableProperty] private IBrush _statusForeground = Brushes.White;
    [ObservableProperty] private string _progressPercentText = "";

    public string WindowTitle => Loc.Get("window.title");
    public string BannerTitle => Loc.Get("banner.title");
    public string BannerVersion => Loc.Get("banner.version", "0.1-dev");
    public string PlayLabel => Loc.Get("button.play");
    public string ExitLabel => Loc.Get("button.exit");
    public string SettingsTooltip => Loc.Get("button.settings");
    public string CloseTooltip => Loc.Get("button.close");
    public string NewsTitle => Loc.Get("news.title");
    public string NewsEmptyLabel => Loc.Get("news.empty");

    public bool IsNewsEmpty => string.IsNullOrWhiteSpace(NewsText);
    public bool IsNewsPresent => !IsNewsEmpty;

    private LauncherState _state = LauncherState.Idle;
    private LoadedManifest? _lastManifest;
    private RuntimeKey? _lastRuntimeKey;
    private bool _signatureBlocked;

    public MainWindowViewModel()
    {
        _clientRoot = Directory.GetCurrentDirectory();
        _stateDir = Path.Combine(_clientRoot, LauncherConfig.StateDirName);
        Directory.CreateDirectory(_stateDir);
        _settingsPath = Path.Combine(_stateDir, "launcher-settings.json");
        _settings = LauncherSettings.Load(_settingsPath);
        Loc.SetLocale(_settings.Locale);
        Loc.Changed += OnLocaleChanged;

        _http = new HttpClient { Timeout = LauncherConfig.BlobFetchTimeout };
        _http.DefaultRequestHeaders.UserAgent.ParseAdd("Metin2Launcher/0.1");

        _statusText = Loc.Get("status.idle");
        _newsText = "";
    }

    public void AttachWindow(Window w) => _window = w as MainWindow;

    private void OnLocaleChanged()
    {
        OnPropertyChanged(nameof(WindowTitle));
        OnPropertyChanged(nameof(BannerTitle));
        OnPropertyChanged(nameof(BannerVersion));
        OnPropertyChanged(nameof(PlayLabel));
        OnPropertyChanged(nameof(ExitLabel));
        OnPropertyChanged(nameof(SettingsTooltip));
        OnPropertyChanged(nameof(CloseTooltip));
        OnPropertyChanged(nameof(NewsTitle));
        OnPropertyChanged(nameof(NewsEmptyLabel));
        // Refresh status text from current state
        ApplyState(_state);
    }

    public async Task StartUpdateAsync()
    {
        // ~10 Hz UI flush timer
        _flushTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
        _flushTimer.Tick += (_, _) => FlushProgress();
        _flushTimer.Start();

        var orchestrator = new UpdateOrchestrator(_clientRoot, _settings, _http);
        var progress = new Progress<UpdateProgress>(p =>
        {
            lock (_progressLock) { _pendingProgress = p; }
        });

        UpdateOrchestrator.Result? result = null;
        try
        {
            using var cts = new CancellationTokenSource(LauncherConfig.TotalUpdateTimeout);
            result = await Task.Run(() => orchestrator.RunAsync(progress, cts.Token));
        }
        catch (ManifestSignatureException)
        {
            _signatureBlocked = true;
            ApplyState(LauncherState.SignatureFailed);
            FlushProgress();
            return;
        }
        catch (Exception ex)
        {
            Log.Error("update flow crashed, falling back to offline launch", ex);
            ApplyState(LauncherState.OfflineFallback);
        }

        FlushProgress();

        if (result is not null)
        {
            _lastManifest = result.Manifest;
            _lastRuntimeKey = result.RuntimeKey;
            ApplyState(result.FinalState);
            await TryFetchNewsAsync(result.Manifest);
        }

        // Velopack self-update runs alongside the asset update — its outcome does
        // not gate the Play button, but we want the launcher to keep itself current
        // whether the user runs the GUI or --nogui.
        try
        {
            await TryVelopackSelfUpdateAsync().ConfigureAwait(true);
        }
        catch (Exception ex)
        {
            Log.Warn("velopack self-update check failed: " + ex.Message);
        }

        _flushTimer?.Stop();
    }

    private static async Task TryVelopackSelfUpdateAsync()
    {
        var um = new UpdateManager(new SimpleWebSource(LauncherConfig.VelopackFeedUrl));
        if (!um.IsInstalled)
        {
            Log.Info("velopack: not running as an installed app, skipping self-update");
            return;
        }
        var info = await um.CheckForUpdatesAsync().ConfigureAwait(false);
        if (info is null)
        {
            Log.Info("velopack: launcher is up to date");
            return;
        }
        Log.Info($"velopack: downloading launcher update {info.TargetFullRelease?.Version}");
        await um.DownloadUpdatesAsync(info).ConfigureAwait(false);
        Log.Info("velopack: launcher update staged, will apply on next restart");
    }

    private void FlushProgress()
    {
        UpdateProgress? p;
        lock (_progressLock) { p = _pendingProgress; _pendingProgress = null; }
        if (p is null) return;

        if (p.Manifest is not null) _lastManifest = p.Manifest;
        ApplyState(p.State);

        if (p.Percent.HasValue)
        {
            IsIndeterminate = false;
            ProgressValue = p.Percent.Value;
            ProgressPercentText = $"{(int)p.Percent.Value}%";
        }
        else
        {
            IsIndeterminate = true;
            ProgressPercentText = "";
        }

        if (!string.IsNullOrEmpty(p.Detail))
            DetailText = p.Detail!;
    }

    private void ApplyState(LauncherState s)
    {
        _state = s;
        StatusText = s switch
        {
            LauncherState.Idle => Loc.Get("status.idle"),
            LauncherState.FetchingManifest => Loc.Get("status.fetchingManifest"),
            LauncherState.VerifyingSignature => Loc.Get("status.verifyingSignature"),
            LauncherState.Diffing => Loc.Get("status.diffing"),
            LauncherState.Downloading => Loc.Get("status.downloading"),
            LauncherState.Applying => Loc.Get("status.applying"),
            LauncherState.UpToDate => Loc.Get("status.upToDate"),
            LauncherState.OfflineFallback => Loc.Get("status.offlineFallback"),
            LauncherState.SignatureFailed => Loc.Get("status.signatureFailed"),
            LauncherState.Launching => Loc.Get("status.launching"),
            _ => "",
        };

        // Dark theme: red on critical failure, white on everything else.
        StatusForeground = s == LauncherState.SignatureFailed
            ? new SolidColorBrush(Color.FromRgb(0xFF, 0x50, 0x50))
            : Brushes.White;

        var indeterminate = s is LauncherState.FetchingManifest
            or LauncherState.VerifyingSignature
            or LauncherState.Diffing;
        IsIndeterminate = indeterminate;

        if (s is LauncherState.UpToDate or LauncherState.OfflineFallback)
        {
            ProgressValue = 100;
            ProgressPercentText = "100%";
        }

        CanPlay = !_signatureBlocked
                  && s is LauncherState.UpToDate
                              or LauncherState.OfflineFallback;
    }

    private async Task TryFetchNewsAsync(LoadedManifest? loaded)
    {
        if (loaded is null)
        {
            // Leave NewsText empty so the XAML empty-state placeholder renders.
            NewsText = "";
            return;
        }

        var sb = new StringBuilder();
        sb.AppendLine($"v{loaded.Manifest.Version}  ({loaded.Manifest.CreatedAt})");
        sb.AppendLine();
        sb.AppendLine(string.IsNullOrWhiteSpace(loaded.Manifest.Notes) ? "—" : loaded.Manifest.Notes);

        var prev = loaded.Manifest.Previous;
        if (!string.IsNullOrEmpty(prev))
        {
            try
            {
                var manifestUrl = _settings.EffectiveManifestUrl();
                var baseUri = new Uri(manifestUrl);
                var prevUrl = new Uri(baseUri, $"manifests/{prev}.json").ToString();
                var json = await _http.GetStringAsync(prevUrl);
                var prevManifest = System.Text.Json.JsonSerializer.Deserialize<Manifest.Manifest>(json);
                if (prevManifest is not null && !string.IsNullOrWhiteSpace(prevManifest.Notes))
                {
                    sb.AppendLine();
                    sb.AppendLine();
                    sb.AppendLine(Loc.Get("news.previousHeader", prev));
                    sb.AppendLine(prevManifest.Notes);
                }
            }
            catch
            {
                // best effort
            }
        }

        NewsText = sb.ToString();
    }

    [RelayCommand]
    private void Exit()
    {
        if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime d)
            d.Shutdown(0);
        else
            Environment.Exit(0);
    }

    [RelayCommand]
    private void Play()
    {
        if (!CanPlay) return;
        ApplyState(LauncherState.Launching);
        var ok = GameProcess.Launch(_clientRoot, LauncherConfig.GameExecutable, _lastRuntimeKey);
        if (ok)
        {
            if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime d)
                d.Shutdown(0);
        }
        else
        {
            // Re-enable so user can retry
            CanPlay = true;
        }
    }

    [RelayCommand]
    private async Task OpenSettings()
    {
        if (_window is null) return;
        var dialogVm = new SettingsDialogViewModel(_settings);
        var dialog = new SettingsDialog { DataContext = dialogVm };
        var ok = await dialog.ShowDialog<bool>(_window);
        if (!ok) return;

        dialogVm.ApplyTo(_settings);
        _settings.Save(_settingsPath);
        Loc.SetLocale(_settings.Locale);
    }
}
