using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using NexusShell.Models;

namespace NexusShell.Services;

public sealed class UpdateService
{
    private readonly HttpClient _httpClient;
    private readonly string _manifestUrl;
    private readonly string _currentVersion;
    private readonly int _currentBuild;
    private readonly bool _autoInstall;
    private UpdateCheckResult? _lastResult;
    private ClientUpdateFlowState _flowState = ClientUpdateFlowState.Idle;
    private string? _stagedZipPath;
    private CancellationTokenSource? _downloadCts;

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true,
    };

    public UpdateService(
        HttpClient httpClient,
        string manifestUrl,
        string currentVersion,
        int currentBuild,
        bool autoInstall = true)
    {
        _httpClient = httpClient;
        _manifestUrl = manifestUrl;
        _currentVersion = currentVersion;
        _currentBuild = currentBuild;
        _autoInstall = autoInstall;
        RecoverStaleJobOnStartup();
    }

    private void RecoverStaleJobOnStartup()
    {
        var recovered = NexusPlatformUpdateJobStore.RecoverStaleJob(out var errorMessage);
        if (recovered is null)
        {
            return;
        }

        _flowState = recovered.Value;
        _lastResult = new UpdateCheckResult
        {
            Status = UpdateCheckStatus.Error,
            ErrorMessage = errorMessage,
        };
    }

    public bool AutoInstallEnabled => _autoInstall;

    public string CurrentVersion => _currentVersion;
    public int CurrentBuild => _currentBuild;
    public string ManifestUrl => _manifestUrl;
    public UpdateCheckResult? LastResult => _lastResult;
    public ClientUpdateFlowState FlowState => _flowState;
    public string? StagedZipPath => _stagedZipPath;

    public string StatusSummary => _flowState switch
    {
        ClientUpdateFlowState.Downloading => "Updates: Downloading…",
        ClientUpdateFlowState.Validating => "Updates: Validating…",
        ClientUpdateFlowState.Ready => "Updates: Ready to install",
        ClientUpdateFlowState.Preparing => "Updates: Preparing install…",
        ClientUpdateFlowState.Installing => "Updates: Installing…",
        ClientUpdateFlowState.Blocked => "Updates: Blocked",
        ClientUpdateFlowState.Failed => "Updates: Failed",
        _ => _lastResult?.Status switch
        {
            UpdateCheckStatus.UpToDate => "Updates: Up to date",
            UpdateCheckStatus.UpdateAvailable =>
                $"Update available: {_lastResult.RemoteManifest?.Version ?? "unknown"}",
            UpdateCheckStatus.LocalBuildAhead => "Updates: Local dev build ahead",
            UpdateCheckStatus.ChannelUnavailable => "Update channel unavailable",
            UpdateCheckStatus.Checking => "Updates: Checking…",
            UpdateCheckStatus.ClientTooOld => "Client build too old for update",
            UpdateCheckStatus.Error => "Update channel unavailable",
            _ => "Updates: Not checked",
        },
    };

    public async Task<UpdateCheckResult> CheckForUpdatesAsync(CancellationToken cancellationToken = default)
    {
        _lastResult = new UpdateCheckResult { Status = UpdateCheckStatus.Checking };
        _flowState = ClientUpdateFlowState.Idle;
        UpdateLogWriter.Info($"Manifest fetch started: {_manifestUrl}");

        try
        {
            using var response = await _httpClient.GetAsync(_manifestUrl, cancellationToken);
            if (!response.IsSuccessStatusCode)
            {
                UpdateLogWriter.Error($"Manifest fetch failed: HTTP {(int)response.StatusCode}");
                _lastResult = new UpdateCheckResult
                {
                    Status = UpdateCheckStatus.ChannelUnavailable,
                    ErrorMessage = $"HTTP {(int)response.StatusCode}",
                };
                return _lastResult;
            }

            var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken);
            var json = StripBom(Encoding.UTF8.GetString(bytes));
            var manifest = JsonSerializer.Deserialize<NexusUpdateManifest>(json, JsonOptions);
            if (manifest is null || string.IsNullOrWhiteSpace(manifest.Version))
            {
                UpdateLogWriter.Error("Manifest parse failed: invalid or missing version");
                _lastResult = new UpdateCheckResult
                {
                    Status = UpdateCheckStatus.Error,
                    ErrorMessage = "Invalid manifest",
                };
                return _lastResult;
            }

            UpdateLogWriter.Info(
                $"Manifest parsed: remote build={manifest.Build} version={manifest.Version} local build={_currentBuild}");

            if (!manifest.MeetsMinimumBuild(_currentBuild))
            {
                UpdateLogWriter.Info(
                    $"Build compare: client build {_currentBuild} below minimumSupportedBuild {manifest.EffectiveMinimumSupportedBuild}");
                _lastResult = new UpdateCheckResult
                {
                    Status = UpdateCheckStatus.ClientTooOld,
                    RemoteManifest = manifest,
                    ErrorMessage =
                        $"Denne installation (build {_currentBuild}) understøtter ikke opdatering til build {manifest.Build}.",
                };
                _flowState = ClientUpdateFlowState.Failed;
                return _lastResult;
            }

            if (manifest.Build < _currentBuild)
            {
                UpdateLogWriter.Info(
                    $"Build compare: local build {_currentBuild} ahead of remote build {manifest.Build}; downgrade blocked");
                _lastResult = new UpdateCheckResult
                {
                    Status = UpdateCheckStatus.LocalBuildAhead,
                    RemoteManifest = manifest,
                    ErrorMessage =
                        $"Du kører en nyere lokal build ({_currentBuild}) end update-kanalen ({manifest.Build}).",
                    IsUpdateAvailable = false,
                };
                _flowState = ClientUpdateFlowState.UpToDate;
                return _lastResult;
            }

            var updateAvailable = NexusUpdateManifest.IsUpdateApplicable(manifest, _currentVersion, _currentBuild);
            UpdateLogWriter.Info($"Build compare: updateAvailable={updateAvailable}");

            _lastResult = new UpdateCheckResult
            {
                Status = updateAvailable ? UpdateCheckStatus.UpdateAvailable : UpdateCheckStatus.UpToDate,
                RemoteManifest = manifest,
                IsUpdateAvailable = updateAvailable,
            };
            _flowState = updateAvailable ? ClientUpdateFlowState.UpdateFound : ClientUpdateFlowState.UpToDate;
            return _lastResult;
        }
        catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or IOException)
        {
            UpdateLogWriter.Error($"Manifest fetch error: {ex.Message}");
            Debug.WriteLine($"[Nexus Update] check failed: {ex.Message}");
            _lastResult = new UpdateCheckResult
            {
                Status = UpdateCheckStatus.ChannelUnavailable,
                ErrorMessage = ex.Message,
            };
            return _lastResult;
        }
        catch (Exception ex)
        {
            UpdateLogWriter.Error($"Manifest unexpected error: {ex.Message}");
            Debug.WriteLine($"[Nexus Update] unexpected: {ex.Message}");
            _lastResult = new UpdateCheckResult
            {
                Status = UpdateCheckStatus.Error,
                ErrorMessage = ex.Message,
            };
            return _lastResult;
        }
    }

    public static bool IsRemoteNewer(NexusUpdateManifest remote, string currentVersion, int currentBuild) =>
        NexusUpdateManifest.IsRemoteNewer(remote, currentVersion, currentBuild);

    private static string StripBom(string text)
    {
        if (text.Length > 0 && text[0] == '\uFEFF')
        {
            return text[1..];
        }

        return text;
    }

    public void CancelDownload()
    {
        _downloadCts?.Cancel();
        _downloadCts = null;
        if (_flowState is ClientUpdateFlowState.Downloading or ClientUpdateFlowState.Validating)
        {
            _flowState = ClientUpdateFlowState.Cancelled;
        }

        UpdateLogWriter.Info("Download cancelled by user");
    }

    /// <summary>
    /// Clears a stuck persisted job (Preparing/Applying/Blocked) so the user can retry or download again.
    /// </summary>
    public void ClearStuckJob()
    {
        NexusPlatformUpdateJobStore.Clear();
        _flowState = _stagedZipPath is not null && File.Exists(_stagedZipPath)
            ? ClientUpdateFlowState.Ready
            : _lastResult?.IsUpdateAvailable == true
                ? ClientUpdateFlowState.UpdateFound
                : ClientUpdateFlowState.Idle;
        UpdateLogWriter.Info("Stuck update job cleared by user");
    }

    public async Task<UpdateApplyResult> DownloadUpdateAsync(
        NexusUpdateManifest manifest,
        IProgress<string>? progress = null,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(manifest.EffectivePackageUrl)
            || string.IsNullOrWhiteSpace(manifest.EffectiveSha256))
        {
            _flowState = ClientUpdateFlowState.Failed;
            UpdateLogWriter.Error("Download blocked: manifest missing package URL or SHA256");
            return new UpdateApplyResult
            {
                Status = UpdateApplyStatus.Failed,
                ErrorMessage = "Manifest mangler artifact.url/packageUrl eller sha256.",
            };
        }

        _downloadCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        var token = _downloadCts.Token;

        try
        {
            _flowState = ClientUpdateFlowState.Downloading;
            progress?.Report("Downloader opdatering…");
            UpdateLogWriter.Info($"Download started: build={manifest.Build}");

            var zipPath = Path.Combine(
                Path.GetTempPath(),
                $"nexus-update-{manifest.Build}-{Guid.NewGuid():N}.zip");
            await DownloadFileAsync(manifest.EffectivePackageUrl, zipPath, token);

            _flowState = ClientUpdateFlowState.Validating;
            progress?.Report("Verificerer SHA256…");
            UpdateLogWriter.Info("SHA256 validation started");

            var hash = await ComputeSha256Async(zipPath, token);
            if (!hash.Equals(manifest.EffectiveSha256, StringComparison.OrdinalIgnoreCase))
            {
                File.Delete(zipPath);
                _flowState = ClientUpdateFlowState.Failed;
                UpdateLogWriter.Error(
                    $"SHA256 mismatch: expected={PreviewHash(manifest.EffectiveSha256)} actual={PreviewHash(hash)}");
                return new UpdateApplyResult
                {
                    Status = UpdateApplyStatus.Failed,
                    ErrorMessage = "SHA256 matcher ikke manifest.",
                };
            }

            if (_stagedZipPath is not null && File.Exists(_stagedZipPath))
            {
                try { File.Delete(_stagedZipPath); } catch { /* best effort */ }
            }

            _stagedZipPath = zipPath;
            _flowState = ClientUpdateFlowState.Ready;
            UpdateLogWriter.Info($"Download complete and verified: {zipPath}");
            return new UpdateApplyResult
            {
                Status = UpdateApplyStatus.Verifying,
                DownloadPath = zipPath,
            };
        }
        catch (OperationCanceledException)
        {
            _flowState = ClientUpdateFlowState.UpdateFound;
            return new UpdateApplyResult { Status = UpdateApplyStatus.Skipped, ErrorMessage = "Download annulleret." };
        }
        catch (Exception ex) when (ex is HttpRequestException or IOException or TaskCanceledException)
        {
            _flowState = ClientUpdateFlowState.Failed;
            UpdateLogWriter.Error($"Download failed: {ex.Message}");
            Debug.WriteLine($"[Nexus Update] download failed: {ex.Message}");
            return new UpdateApplyResult
            {
                Status = UpdateApplyStatus.Failed,
                ErrorMessage = ex.Message,
            };
        }
        finally
        {
            _downloadCts = null;
        }
    }

    public async Task<UpdateApplyResult> TryAutoApplyUpdateAsync(
        IProgress<string>? progress = null,
        CancellationToken cancellationToken = default)
    {
        if (_lastResult is null || !_lastResult.IsUpdateAvailable || _lastResult.RemoteManifest is null)
        {
            return new UpdateApplyResult { Status = UpdateApplyStatus.Skipped };
        }

        return await ApplyManifestAsync(_lastResult.RemoteManifest, progress, cancellationToken);
    }

    public async Task<UpdateApplyResult> ApplyManifestAsync(
        NexusUpdateManifest manifest,
        IProgress<string>? progress = null,
        CancellationToken cancellationToken = default)
    {
        var download = await DownloadUpdateAsync(manifest, progress, cancellationToken);
        if (download.Status == UpdateApplyStatus.Failed || download.Status == UpdateApplyStatus.Skipped)
        {
            return download;
        }

        return await InstallStagedUpdateAsync(progress, cancellationToken);
    }

    public async Task<UpdateApplyResult> InstallStagedUpdateAsync(
        IProgress<string>? progress = null,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(_stagedZipPath) || !File.Exists(_stagedZipPath))
        {
            _flowState = ClientUpdateFlowState.Failed;
            UpdateLogWriter.Error("Install blocked: no staged ZIP");
            return new UpdateApplyResult
            {
                Status = UpdateApplyStatus.Failed,
                ErrorMessage = "Ingen verificeret opdatering klar. Download først.",
            };
        }

        return await InstallStagedZipAsync(_stagedZipPath, progress, cancellationToken);
    }

    public Task<UpdateApplyResult> RetryInstallAsync(
        IProgress<string>? progress = null,
        CancellationToken cancellationToken = default) =>
        InstallStagedUpdateAsync(progress, cancellationToken);

    private async Task<UpdateApplyResult> InstallStagedZipAsync(
        string zipPath,
        IProgress<string>? progress,
        CancellationToken cancellationToken)
    {
        if (!NexusInstallRoot.TryResolve(out var installRoot))
        {
            return FailInstall("Auto-update kræver laptop POC-layout (shell + identity mappe).");
        }

        var applyScript = Path.Combine(installRoot, "apply-pending-update.ps1");
        if (!File.Exists(applyScript))
        {
            return FailInstall("apply-pending-update.ps1 mangler i installationsmappen.");
        }

        var targetBuild = _lastResult?.RemoteManifest?.Build;
        var job = new NexusPlatformUpdateJob
        {
            Phase = PlatformUpdateJobPhase.Preparing,
            ZipPath = zipPath,
            InstallRoot = installRoot,
            TargetBuild = targetBuild,
            StartedUtc = DateTimeOffset.UtcNow,
            UpdatedUtc = DateTimeOffset.UtcNow,
        };
        NexusPlatformUpdateJobStore.Save(job);

        try
        {
            _flowState = ClientUpdateFlowState.Preparing;
            progress?.Report("Afventer — forbereder installation…");
            UpdateLogWriter.Info("Install prepare: stopping Nexus processes");

            using var prepareCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            prepareCts.CancelAfter(NexusPlatformUpdateJobStore.PreparingTimeout);

            await Task.Run(() =>
            {
                cancellationToken.ThrowIfCancellationRequested();
                var stop = NexusUpdateProcessStopper.StopForUpdate(
                    installRoot,
                    includeCurrentShell: false);
                if (!stop.Success)
                {
                    throw new InvalidOperationException(stop.ErrorMessage);
                }
            }, prepareCts.Token);

            progress?.Report("Tjekker fil-låse før opdatering…");
            var lockCheck = NexusUpdateFileLockChecker.CheckInstallLocks(installRoot);
            if (!lockCheck.CanProceed)
            {
                job.Phase = PlatformUpdateJobPhase.Blocked;
                job.ErrorMessage = lockCheck.SummaryMessage;
                job.LockedFile = lockCheck.LockedFiles.FirstOrDefault()?.FilePath;
                NexusPlatformUpdateJobStore.Save(job);
                _flowState = ClientUpdateFlowState.Blocked;
                UpdateLogWriter.Error($"Install blocked by file lock: {lockCheck.SummaryMessage}");
                return new UpdateApplyResult
                {
                    Status = UpdateApplyStatus.Blocked,
                    ErrorMessage = lockCheck.SummaryMessage,
                };
            }

            job.Phase = PlatformUpdateJobPhase.Applying;
            NexusPlatformUpdateJobStore.Save(job);
            _flowState = ClientUpdateFlowState.Installing;
            progress?.Report("Anvender opdatering og genstarter…");
            UpdateLogWriter.Info($"Install started via {applyScript}");

            var buildArg = targetBuild?.ToString() ?? "0";
            var psi = new ProcessStartInfo
            {
                FileName = "powershell.exe",
                Arguments =
                    $"-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File \"{applyScript}\" " +
                    $"-ZipPath \"{zipPath}\" -InstallRoot \"{installRoot}\" -TargetBuild {buildArg}",
                UseShellExecute = false,
                CreateNoWindow = true,
            };

            Process.Start(psi);
            UpdateLogWriter.Info("Install script launched; restart scheduled");
            return new UpdateApplyResult
            {
                Status = UpdateApplyStatus.Scheduled,
                RestartScheduled = true,
            };
        }
        catch (OperationCanceledException)
        {
            return FailInstall(
                "Opdatering hang i 'forbereder installation'. Timeout — prøv igen.",
                PlatformUpdateJobPhase.Failed);
        }
        catch (Exception ex)
        {
            return FailInstall(ex.Message, PlatformUpdateJobPhase.Failed);
        }
    }

    private UpdateApplyResult FailInstall(string message, PlatformUpdateJobPhase phase = PlatformUpdateJobPhase.Failed)
    {
        _flowState = phase == PlatformUpdateJobPhase.Blocked
            ? ClientUpdateFlowState.Blocked
            : ClientUpdateFlowState.Failed;

        var job = NexusPlatformUpdateJobStore.Load();
        if (job is not null)
        {
            job.Phase = phase;
            job.ErrorMessage = message;
            NexusPlatformUpdateJobStore.Save(job);
        }

        UpdateLogWriter.Error($"Install failed: {message}");
        Debug.WriteLine($"[Nexus Update] apply failed: {message}");
        return new UpdateApplyResult
        {
            Status = phase == PlatformUpdateJobPhase.Blocked
                ? UpdateApplyStatus.Blocked
                : UpdateApplyStatus.Failed,
            ErrorMessage = message,
        };
    }

    private async Task DownloadFileAsync(string url, string destination, CancellationToken cancellationToken)
    {
        using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
        response.EnsureSuccessStatusCode();

        await using var input = await response.Content.ReadAsStreamAsync(cancellationToken);
        await using var output = File.Create(destination);
        await input.CopyToAsync(output, cancellationToken);
    }

    private static string PreviewHash(string hash) =>
        hash.Length >= 16 ? $"{hash[..16]}…" : hash;

    private static async Task<string> ComputeSha256Async(string path, CancellationToken cancellationToken)
    {
        await using var stream = File.OpenRead(path);
        var hash = await SHA256.HashDataAsync(stream, cancellationToken);
        return Convert.ToHexString(hash).ToLowerInvariant();
    }
}
