using System.Diagnostics;
using System.Text.Json;
using System.Text.RegularExpressions;
using Sdt.Config;
using Sdt.Runner;

namespace Sdt.Core;

public sealed class PrereqInstallerService : IPrereqInstaller
{
    public async Task<InstallPlan> GetInstallPlanAsync(
        string tool,
        string projectRoot,
        DevToolConfig? config = null,
        CancellationToken cancellationToken = default)
    {
        var fromConfig = TryGetPlanFromConfig(tool, config);
        if (fromConfig is not null)
            return fromConfig;

        var scriptPath = ScriptLocator.FindHelperScript(projectRoot, "diag.py");
        if (scriptPath is null)
            return FallbackPlan(tool);

        try
        {
            var psi = new ProcessStartInfo
            {
                FileName = PythonResolver.ResolveExecutable(),
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
                WorkingDirectory = projectRoot,
            };
            psi.ArgumentList.Add(scriptPath);
            psi.ArgumentList.Add("install-plan");
            psi.ArgumentList.Add("--tool");
            psi.ArgumentList.Add(tool);
            psi.ArgumentList.Add("--json");

            using var process = new Process { StartInfo = psi };
            process.Start();
            var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
            var stderr = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
            await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);

            if (process.ExitCode != 0)
            {
                var fallback = FallbackPlan(tool);
                return fallback with
                {
                    Summary = $"diag.py install-plan failed for {tool}; using fallback templates. " +
                              $"{(string.IsNullOrWhiteSpace(stderr) ? "No stderr output." : stderr.Trim())}"
                };
            }

            var parsed = JsonSerializer.Deserialize<InstallPlanJson>(stdout);
            if (parsed is null)
            {
                var fallback = FallbackPlan(tool);
                return fallback with { Summary = $"diag.py returned invalid JSON for {tool}; using fallback templates." };
            }

            var commands = parsed.Commands
                .Select(c => new InstallCommand(c.Command ?? "", c.Args ?? []))
                .Where(c => !string.IsNullOrWhiteSpace(c.Command))
                .ToList();
            if (!parsed.Supported || commands.Count == 0)
            {
                var fallback = FallbackPlan(tool);
                return fallback with { Summary = $"diag.py returned no usable commands for {tool}; using fallback templates." };
            }

            return new InstallPlan(
                parsed.Tool ?? tool,
                parsed.Supported,
                parsed.Summary ?? $"Install {tool}",
                commands);
        }
        catch (Exception ex)
        {
            var fallback = FallbackPlan(tool);
            return fallback with { Summary = $"diag.py install-plan exception for {tool}; using fallback templates. {ex.Message}" };
        }
    }

    public Task<RunResult> RunInstallAsync(
        InstallCommand command,
        string projectRoot,
        Action<string, bool> onOutput,
        CancellationToken cancellationToken = default)
        => ProcessRunner.RunAsync(command.Command, command.Args, projectRoot, onOutput, cancellationToken: cancellationToken);

    private static InstallPlan FallbackPlan(string tool)
    {
        var isWindows = OperatingSystem.IsWindows();
        var normalized = tool.ToLowerInvariant();
        if (normalized == "tauri")
            return BuildTauriFallbackPlan();

        var installCommand = normalized switch
        {
            "dotnet" => isWindows
                ? new InstallCommand("winget", ["install", "Microsoft.DotNet.SDK.10"])
                : new InstallCommand("sh", ["-c", "echo Install dotnet SDK from your distro package manager"]),
            "node" => isWindows
                ? new InstallCommand("winget", ["install", "OpenJS.NodeJS.LTS"])
                : new InstallCommand("sh", ["-c", "echo Install nodejs via package manager"]),
            "npm" => isWindows
                ? new InstallCommand("winget", ["install", "OpenJS.NodeJS.LTS"])
                : new InstallCommand("sh", ["-c", "echo Install npm via package manager"]),
            "cargo" => isWindows
                ? new InstallCommand("winget", ["install", "Rustlang.Rustup"])
                : new InstallCommand("sh", ["-c", "curl https://sh.rustup.rs -sSf | sh"]),
            "git" => isWindows
                ? new InstallCommand("winget", ["install", "Git.Git"])
                : new InstallCommand("sh", ["-c", "echo Install git via package manager"]),
            "docker" => isWindows
                ? new InstallCommand("winget", ["install", "Docker.DockerDesktop"])
                : new InstallCommand("sh", ["-c", "echo Install docker engine via package manager"]),
            "python" => isWindows
                ? new InstallCommand("winget", ["install", "Python.Python.3.12"])
                : new InstallCommand("sh", ["-c", "echo Install python3 via package manager"]),
            _ => new InstallCommand("sh", ["-c", $"echo No installer template for '{tool}'"]),
        };

        return new InstallPlan(
            tool,
            Supported: true,
            Summary: $"Fallback install plan for {tool}",
            Commands: [installCommand]);
    }

    private static InstallPlan BuildTauriFallbackPlan()
    {
        if (OperatingSystem.IsWindows())
        {
            return new InstallPlan(
                "tauri",
                Supported: true,
                Summary: "Fallback tauri plan (Windows): install Node.js, Rust toolchain, and Tauri CLI. Visual Studio C++ build tools/WebView2 may also be required.",
                Commands:
                [
                    new InstallCommand("winget", ["install", "OpenJS.NodeJS.LTS"]),
                    new InstallCommand("winget", ["install", "Rustlang.Rustup"]),
                    new InstallCommand("npm", ["install", "-g", "@tauri-apps/cli"]),
                ]);
        }

        if (OperatingSystem.IsMacOS())
        {
            return new InstallPlan(
                "tauri",
                Supported: true,
                Summary: "Fallback tauri plan (macOS): install Xcode command line tools, Rust toolchain, and Tauri CLI.",
                Commands:
                [
                    new InstallCommand("sh", ["-c", "xcode-select --install || true"]),
                    new InstallCommand("sh", ["-c", "curl https://sh.rustup.rs -sSf | sh -s -- -y"]),
                    new InstallCommand("npm", ["install", "-g", "@tauri-apps/cli"]),
                ]);
        }

        var linuxPlan = BuildLinuxTauriPrereqCommand();
        return new InstallPlan(
            "tauri",
            Supported: true,
            Summary: $"Fallback tauri plan (Linux): detected package manager `{linuxPlan.PackageManager}` for system deps, then install Rust toolchain and Tauri CLI.",
            Commands:
            [
                new InstallCommand("sh", ["-c", linuxPlan.Command]),
                new InstallCommand("sh", ["-c", "curl https://sh.rustup.rs -sSf | sh -s -- -y"]),
                new InstallCommand("npm", ["install", "-g", "@tauri-apps/cli"]),
            ]);
    }

    private static (string PackageManager, string Command) BuildLinuxTauriPrereqCommand()
    {
        if (CommandExists("apt-get"))
        {
            return ("apt-get",
                "sudo apt-get update && sudo apt-get install -y build-essential libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev");
        }

        if (CommandExists("dnf"))
        {
            return ("dnf",
                "sudo dnf install -y gcc gcc-c++ make webkit2gtk4.1-devel gtk3-devel libappindicator-gtk3 librsvg2-devel");
        }

        if (CommandExists("pacman"))
        {
            return ("pacman",
                "sudo pacman -S --needed base-devel webkit2gtk gtk3 libappindicator-gtk3 librsvg");
        }

        if (CommandExists("zypper"))
        {
            return ("zypper",
                "sudo zypper install -y gcc gcc-c++ make webkit2gtk3-devel gtk3-devel libappindicator3-devel librsvg-devel");
        }

        if (CommandExists("apk"))
        {
            return ("apk",
                "sudo apk add build-base webkit2gtk-dev gtk+3.0-dev libayatana-appindicator-dev librsvg-dev");
        }

        return ("unknown",
            "echo Install tauri system dependencies using your distro package manager, then rerun SDT.");
    }

    private static bool CommandExists(string command)
    {
        try
        {
            var psi = new ProcessStartInfo
            {
                FileName = OperatingSystem.IsWindows() ? "where" : "which",
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
            };
            psi.ArgumentList.Add(command);
            using var process = Process.Start(psi);
            if (process is null)
                return false;
            process.WaitForExit(1500);
            return process.ExitCode == 0;
        }
        catch
        {
            return false;
        }
    }

    private static InstallPlan? TryGetPlanFromConfig(string tool, DevToolConfig? config)
    {
        var preferred = config?.Tooling?.Tools
            .FirstOrDefault(t => string.Equals(t.Tool, tool, StringComparison.OrdinalIgnoreCase))
            ?.PreferredInstallCommands;

        if (preferred is null || preferred.Count == 0)
            return null;

        var commands = new List<InstallCommand>();
        foreach (var line in preferred)
        {
            var parts = SplitShellLike(line);
            if (parts.Count == 0)
                continue;

            commands.Add(new InstallCommand(parts[0], parts.Skip(1).ToList()));
        }

        if (commands.Count == 0)
            return null;

        return new InstallPlan(
            tool,
            Supported: true,
            Summary: $"Configured install commands for {tool}",
            Commands: commands);
    }

    private static List<string> SplitShellLike(string input)
    {
        if (string.IsNullOrWhiteSpace(input))
            return [];

        var tokens = new List<string>();
        var matches = Regex.Matches(input, "\"([^\"]*)\"|'([^']*)'|\\S+");
        foreach (Match match in matches)
        {
            var value = match.Value.Trim();
            if (value.Length >= 2 && (
                (value.StartsWith("\"", StringComparison.Ordinal) && value.EndsWith("\"", StringComparison.Ordinal)) ||
                (value.StartsWith("'", StringComparison.Ordinal) && value.EndsWith("'", StringComparison.Ordinal))))
            {
                value = value[1..^1];
            }
            if (!string.IsNullOrWhiteSpace(value))
                tokens.Add(value);
        }
        return tokens;
    }

    private sealed class InstallPlanJson
    {
        public string? Tool { get; init; }
        public bool Supported { get; init; }
        public string? Summary { get; init; }
        public List<InstallCommandJson> Commands { get; init; } = [];
    }

    private sealed class InstallCommandJson
    {
        public string? Command { get; init; }
        public List<string>? Args { get; init; }
    }
}
