using System.Diagnostics;
using Xunit;

namespace DevTool.Tests;

public sealed class ScriptCommonTests
{
    [Fact]
    public async Task ResolveRepoRoot_UsesGlobRootHints()
    {
        var root = CreateTempDir("sdt-script-root-");
        var nested = Path.Combine(root, "src", "app");
        Directory.CreateDirectory(nested);
        await File.WriteAllTextAsync(Path.Combine(root, "sample.sln"), "");
        await File.WriteAllTextAsync(Path.Combine(root, "devtool.json"), """
{
  "name": "demo",
  "version": "0.1.0",
  "workflows": [],
  "project": {
    "rootHints": ["*.sln"]
  }
}
""");

        var output = await RunPythonAsync(nested, "import script_common; print(script_common.resolve_repo_root(r'" + Escape(nested) + "'))");
        Assert.Equal(Path.GetFullPath(root), output.Trim());
    }

    [Fact]
    public async Task ResolveRepoRoot_UsesDirectoryMarkerHints()
    {
        var root = CreateTempDir("sdt-script-root-");
        var nested = Path.Combine(root, "child", "leaf");
        Directory.CreateDirectory(nested);
        Directory.CreateDirectory(Path.Combine(root, ".git"));
        await File.WriteAllTextAsync(Path.Combine(root, "devtool.json"), """
{
  "name": "demo",
  "version": "0.1.0",
  "workflows": [],
  "project": {
    "rootHints": [".git", "package.json"]
  }
}
""");

        var output = await RunPythonAsync(nested, "import script_common; print(script_common.resolve_repo_root(r'" + Escape(nested) + "'))");
        Assert.Equal(Path.GetFullPath(root), output.Trim());
    }

    [Fact]
    public async Task ResolveCommand_ExpandsWindowsPathTokens()
    {
        if (!OperatingSystem.IsWindows())
            return;

        var root = CreateTempDir("sdt-script-cmd-");
        var shimDir = Path.Combine(root, "nodejs");
        Directory.CreateDirectory(shimDir);
        await File.WriteAllTextAsync(Path.Combine(shimDir, "npm.cmd"), "@echo off");

        var output = await RunPythonAsync(
            root,
            "import script_common; print(script_common.resolve_command('npm'))",
            new Dictionary<string, string?>
            {
                ["NVM_HOME"] = root,
                ["NVM_SYMLINK"] = shimDir,
                ["PATH"] = "%NVM_HOME%;%NVM_SYMLINK%",
            });

        Assert.EndsWith("npm.cmd", output.Trim(), StringComparison.OrdinalIgnoreCase);
    }

    private static async Task<string> RunPythonAsync(
        string workingDir,
        string script,
        IReadOnlyDictionary<string, string?>? env = null)
    {
        var python = ResolvePython();
        var psi = new ProcessStartInfo
        {
            FileName = python,
            WorkingDirectory = workingDir,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };
        psi.ArgumentList.Add("-c");
        psi.ArgumentList.Add($"import sys; sys.path.insert(0, r'{Escape(Path.Combine(ProjectRepoRoot(), "scripts"))}'); {script}");

        if (env is not null)
        {
            foreach (var pair in env)
                psi.Environment[pair.Key] = pair.Value ?? string.Empty;
        }

        using var process = new Process { StartInfo = psi };
        process.Start();
        var stdout = await process.StandardOutput.ReadToEndAsync();
        var stderr = await process.StandardError.ReadToEndAsync();
        await process.WaitForExitAsync();
        if (process.ExitCode != 0)
            throw new InvalidOperationException($"Python exited {process.ExitCode}: {stderr}");
        return stdout;
    }

    private static string ResolvePython()
    {
        var candidates = OperatingSystem.IsWindows() ? new[] { "python", "py" } : new[] { "python3", "python" };
        foreach (var candidate in candidates)
        {
            try
            {
                using var process = new Process
                {
                    StartInfo = new ProcessStartInfo
                    {
                        FileName = candidate,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true,
                        UseShellExecute = false,
                        CreateNoWindow = true,
                    }
                };
                process.StartInfo.ArgumentList.Add("--version");
                process.Start();
                process.WaitForExit(2000);
                if (process.ExitCode == 0)
                    return candidate;
            }
            catch
            {
            }
        }

        throw new InvalidOperationException("Python executable not found.");
    }

    private static string CreateTempDir(string prefix)
    {
        var path = Path.Combine(Path.GetTempPath(), prefix + Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(path);
        return path;
    }

    private static string Escape(string value) => value.Replace("\\", "\\\\").Replace("'", "\\'");

    private static string ProjectRepoRoot()
    {
        var dir = new DirectoryInfo(AppContext.BaseDirectory);
        while (dir is not null)
        {
            if (File.Exists(Path.Combine(dir.FullName, "devtool.json")) &&
                File.Exists(Path.Combine(dir.FullName, "scripts", "script_common.py")))
            {
                return dir.FullName;
            }
            dir = dir.Parent;
        }

        throw new InvalidOperationException("Could not locate project repo root.");
    }
}
