using System.Diagnostics;
using System.Reflection;
using Microsoft.Win32;
using NexusInstaller.Models;

namespace NexusInstaller.Services;

internal sealed class PrerequisiteService
{
    private const string DotNet8DownloadUrl =
        "https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.16-windows-x64-installer";

    private const string DotNet10DownloadUrl =
        "https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-10.0.6-windows-x64-installer";

    private const string WebView2DownloadUrl =
        "https://go.microsoft.com/fwlink/p/?LinkId=2124703";

    private const string VcRedistDownloadUrl =
        "https://aka.ms/vs/17/release/vc_redist.x64.exe";

    private static readonly Dictionary<PrerequisiteKind, string> EmbeddedFileNames = new()
    {
        [PrerequisiteKind.DotNet8Desktop] = "dotnet-desktop-runtime-8-win-x64.exe",
        [PrerequisiteKind.DotNet10Desktop] = "dotnet-desktop-runtime-10-win-x64.exe",
        [PrerequisiteKind.WebView2] = "MicrosoftEdgeWebview2Setup.exe",
        [PrerequisiteKind.VcRedist] = "vc_redist.x64.exe",
    };

    private static readonly Dictionary<PrerequisiteKind, string> DownloadUrls = new()
    {
        [PrerequisiteKind.DotNet8Desktop] = DotNet8DownloadUrl,
        [PrerequisiteKind.DotNet10Desktop] = DotNet10DownloadUrl,
        [PrerequisiteKind.WebView2] = WebView2DownloadUrl,
        [PrerequisiteKind.VcRedist] = VcRedistDownloadUrl,
    };

    private readonly InstallerLogService _log = InstallerLogService.Instance;

    public async Task<bool> IsInstalledAsync(PrerequisiteKind kind) => kind switch
    {
        PrerequisiteKind.DotNet8Desktop => await RuntimeChecker.HasDotNet8DesktopAsync(),
        PrerequisiteKind.DotNet10Desktop => await RuntimeChecker.HasDotNet10DesktopAsync(),
        PrerequisiteKind.WebView2 => IsWebView2Installed(),
        PrerequisiteKind.VcRedist => IsVcRedistInstalled(),
        _ => true,
    };

    public async Task InstallIfMissingAsync(
        PrerequisiteKind kind,
        IProgress<string>? progress = null,
        CancellationToken cancellationToken = default)
    {
        if (await IsInstalledAsync(kind).ConfigureAwait(false))
        {
            _log.Info($"{KindLabel(kind)} allerede installeret — springer over.");
            progress?.Report($"{KindLabel(kind)} allerede installeret.");
            return;
        }

        _log.Info($"Installerer {KindLabel(kind)}...");
        progress?.Report($"Installerer {KindLabel(kind)}...");

        var installerPath = await ResolveInstallerPathAsync(kind, cancellationToken).ConfigureAwait(false);
        var exitCode = await RunInstallerAsync(kind, installerPath, cancellationToken).ConfigureAwait(false);

        if (!IsSuccessExitCode(exitCode))
        {
            throw new InvalidOperationException(
                $"{KindLabel(kind)} installation fejlede (exit code {exitCode}). Se setup-log.txt for detaljer.");
        }

        if (!await IsInstalledAsync(kind).ConfigureAwait(false))
        {
            throw new InvalidOperationException(
                $"{KindLabel(kind)} blev kørt, men kunne ikke verificeres efter installation.");
        }

        _log.Info($"{KindLabel(kind)} installeret og verificeret.");
        progress?.Report($"{KindLabel(kind)} installeret.");
    }

    public static bool IsWebView2Installed()
    {
        const string clientKey =
            @"SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}";

        using var key = Registry.LocalMachine.OpenSubKey(clientKey);
        if (key?.GetValue("pv") is string version && !string.IsNullOrWhiteSpace(version) && version != "0.0.0.0")
        {
            return true;
        }

        var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
        var webViewPath = Path.Combine(programFiles, "Microsoft", "EdgeWebView", "Application");
        return Directory.Exists(webViewPath) && Directory.EnumerateFiles(webViewPath, "msedgewebview2.exe").Any();
    }

    public static bool IsVcRedistInstalled()
    {
        using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64");
        if (key?.GetValue("Installed") is int installed && installed == 1)
        {
            return true;
        }

        using var wowKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x64");
        return wowKey?.GetValue("Installed") is int wowInstalled && wowInstalled == 1;
    }

    private async Task<string> ResolveInstallerPathAsync(PrerequisiteKind kind, CancellationToken cancellationToken)
    {
        var embedded = TryExtractEmbedded(kind);
        if (embedded is not null)
        {
            _log.Info($"Bruger indlejret installer for {KindLabel(kind)}.");
            return embedded;
        }

        _log.Info($"Downloader {KindLabel(kind)} fra officiel Microsoft-kilde...");
        return await DownloadInstallerAsync(kind, cancellationToken).ConfigureAwait(false);
    }

    private string? TryExtractEmbedded(PrerequisiteKind kind)
    {
        if (!EmbeddedFileNames.TryGetValue(kind, out var fileName))
        {
            return null;
        }

        var assembly = Assembly.GetExecutingAssembly();
        var resourceName = assembly.GetManifestResourceNames()
            .FirstOrDefault(n => n.EndsWith(fileName, StringComparison.OrdinalIgnoreCase));

        if (resourceName is null)
        {
            return null;
        }

        var tempDir = Path.Combine(Path.GetTempPath(), "NexusSetup", "prerequisites");
        Directory.CreateDirectory(tempDir);
        var target = Path.Combine(tempDir, fileName);

        using var stream = assembly.GetManifestResourceStream(resourceName);
        if (stream is null)
        {
            return null;
        }

        using var file = File.Create(target);
        stream.CopyTo(file);
        return target;
    }

    private async Task<string> DownloadInstallerAsync(PrerequisiteKind kind, CancellationToken cancellationToken)
    {
        if (!DownloadUrls.TryGetValue(kind, out var url))
        {
            throw new InvalidOperationException($"Ingen download-URL for {KindLabel(kind)}.");
        }

        var tempDir = Path.Combine(Path.GetTempPath(), "NexusSetup", "downloads");
        Directory.CreateDirectory(tempDir);
        var fileName = EmbeddedFileNames.TryGetValue(kind, out var name)
            ? name
            : $"{kind}-installer.exe";
        var target = Path.Combine(tempDir, fileName);

        try
        {
            using var http = new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
            using var response = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
                .ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                throw new InvalidOperationException(
                    $"Kunne ikke hente {KindLabel(kind)} (HTTP {(int)response.StatusCode}). Tjek netværksforbindelsen.");
            }

            await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
            await using var file = File.Create(target);
            await stream.CopyToAsync(file, cancellationToken).ConfigureAwait(false);
            return target;
        }
        catch (Exception ex) when (ex is not InvalidOperationException)
        {
            throw new InvalidOperationException(
                $"Kunne ikke downloade {KindLabel(kind)}. Tjek netværksforbindelsen og prøv igen.\n{ex.Message}");
        }
    }

    private async Task<int> RunInstallerAsync(
        PrerequisiteKind kind,
        string installerPath,
        CancellationToken cancellationToken)
    {
        var arguments = kind switch
        {
            PrerequisiteKind.WebView2 => "/silent /install",
            _ => "/install /quiet /norestart",
        };

        var psi = new ProcessStartInfo
        {
            FileName = installerPath,
            Arguments = arguments,
            UseShellExecute = false,
            CreateNoWindow = true,
        };

        using var process = Process.Start(psi)
            ?? throw new InvalidOperationException($"Kunne ikke starte {Path.GetFileName(installerPath)}.");

        await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
        _log.Info($"{KindLabel(kind)} installer afsluttet med exit code {process.ExitCode}.");
        return process.ExitCode;
    }

    private static bool IsSuccessExitCode(int exitCode) =>
        exitCode is 0 or 1638 or 3010;

    private static string KindLabel(PrerequisiteKind kind) => kind switch
    {
        PrerequisiteKind.DotNet8Desktop => ".NET 8 Desktop Runtime",
        PrerequisiteKind.DotNet10Desktop => ".NET 10 Desktop Runtime",
        PrerequisiteKind.WebView2 => "WebView2 Runtime",
        PrerequisiteKind.VcRedist => "Visual C++ Redistributable (x64)",
        _ => kind.ToString(),
    };
}
