using Sdt.Config;
using Sdt.Runner;

namespace Sdt.Core;

public sealed class ActionRunner : IActionRunner
{
    public async Task<RunResult> RunStepAsync(
        WorkflowStep step,
        string projectRoot,
        Action<string, bool> onOutput,
        CancellationToken cancellationToken = default)
    {
        if (!string.IsNullOrWhiteSpace(step.Action))
        {
            var scriptPath = ScriptLocator.FindHelperScript(projectRoot, "build.py");
            if (scriptPath is null)
                throw new InvalidOperationException("build.py not found in bundled scripts or project scripts directory.");

            var actionArgs = new List<string>
            {
                scriptPath,
                step.Action,
                "--project-root",
                projectRoot,
            };
            actionArgs.AddRange(step.ActionArgs);

            return await ProcessRunner.RunAsync(
                PythonResolver.ResolveExecutable(),
                actionArgs,
                projectRoot,
                onOutput,
                cancellationToken: cancellationToken).ConfigureAwait(false);
        }

        if (string.IsNullOrWhiteSpace(step.Command))
            return new RunResult(0, TimeSpan.Zero);

        var workingDir = Path.GetFullPath(Path.Combine(projectRoot, step.WorkingDir));

        var pwshReroute = await TryRunLegacyPwshScriptViaPythonAsync(
            step,
            projectRoot,
            workingDir,
            onOutput,
            cancellationToken).ConfigureAwait(false);
        if (pwshReroute is not null)
            return pwshReroute;

        return await ProcessRunner.RunAsync(
            step.Command,
            step.Args,
            workingDir,
            onOutput,
            cancellationToken: cancellationToken).ConfigureAwait(false);
    }

    private static async Task<RunResult?> TryRunLegacyPwshScriptViaPythonAsync(
        WorkflowStep step,
        string projectRoot,
        string workingDir,
        Action<string, bool> onOutput,
        CancellationToken cancellationToken)
    {
        if (!IsPowerShellCommand(step.Command))
            return null;

        var args = step.Args;
        var fileIndex = FindArgIndex(args, "-File");
        if (fileIndex < 0 || fileIndex + 1 >= args.Count)
            return null;

        var psScriptArg = args[fileIndex + 1];
        if (!psScriptArg.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase))
            return null;

        var pyScriptPath = ResolvePythonScriptPath(projectRoot, workingDir, psScriptArg);
        if (pyScriptPath is null)
            return null;

        var translated = TranslatePowerShellArgsToPython(args.Skip(fileIndex + 2));
        var pythonArgs = new List<string> { pyScriptPath };
        pythonArgs.AddRange(translated);

        onOutput($"Legacy PowerShell target detected. Trying Python script first: {Path.GetFileName(pyScriptPath)}", false);

        var pyRun = await ProcessRunner.RunAsync(
            PythonResolver.ResolveExecutable(),
            pythonArgs,
            workingDir,
            onOutput,
            cancellationToken: cancellationToken).ConfigureAwait(false);

        if (pyRun.Success)
            return pyRun;

        var psScriptPath = ResolveScriptPath(workingDir, psScriptArg);
        if (psScriptPath is null || !File.Exists(psScriptPath))
            return pyRun;

        onOutput(
            $"Python script failed (exit {pyRun.ExitCode}). Falling back to legacy PowerShell script: {psScriptArg}",
            true);
        return null;
    }

    private static string? ResolvePythonScriptPath(string projectRoot, string workingDir, string psScriptArg)
    {
        var pyArg = Path.ChangeExtension(psScriptArg, ".py");
        var candidate = ResolveScriptPath(workingDir, pyArg);
        if (candidate is not null && File.Exists(candidate))
            return candidate;

        var fileName = Path.GetFileName(pyArg);
        return ScriptLocator.FindHelperScript(projectRoot, fileName);
    }

    private static string? ResolveScriptPath(string workingDir, string scriptArg)
    {
        if (Path.IsPathRooted(scriptArg))
            return scriptArg;
        return Path.GetFullPath(Path.Combine(workingDir, scriptArg));
    }

    private static bool IsPowerShellCommand(string? command)
    {
        if (string.IsNullOrWhiteSpace(command))
            return false;

        var normalized = Path.GetFileNameWithoutExtension(command).ToLowerInvariant();
        return normalized is "pwsh" or "powershell";
    }

    private static int FindArgIndex(IReadOnlyList<string> args, string name)
    {
        for (var i = 0; i < args.Count; i++)
        {
            if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase))
                return i;
        }
        return -1;
    }

    private static List<string> TranslatePowerShellArgsToPython(IEnumerable<string> inputArgs)
    {
        var result = new List<string>();
        var list = inputArgs.ToList();

        for (var i = 0; i < list.Count; i++)
        {
            var token = list[i];
            if (!token.StartsWith("-", StringComparison.Ordinal) || token == "-")
            {
                result.Add(token);
                continue;
            }

            var key = token.TrimStart('-');
            if (key.Length == 0)
                continue;

            var mapped = MapPowerShellParameter(key);
            var nextIsValue = (i + 1) < list.Count && !list[i + 1].StartsWith("-", StringComparison.Ordinal);
            result.Add(mapped);
            if (nextIsValue)
            {
                result.Add(list[i + 1]);
                i++;
            }
        }

        return result;
    }

    private static string MapPowerShellParameter(string key)
    {
        return key.ToLowerInvariant() switch
        {
            "tauribundles" => "--tauri-bundles",
            "projectroot" => "--project-root",
            "reporoot" => "--repo-root",
            "outputzip" => "--output-zip",
            "inputzip" => "--input-zip",
            "workingdir" => "--working-dir",
            "outputdir" => "--output-dir",
            _ => "--" + ToKebabCase(key)
        };
    }

    private static string ToKebabCase(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            return value.ToLowerInvariant();

        var chars = new List<char>(value.Length + 4);
        for (var i = 0; i < value.Length; i++)
        {
            var c = value[i];
            if (char.IsUpper(c) && i > 0 && value[i - 1] != '-')
                chars.Add('-');
            chars.Add(char.ToLowerInvariant(c));
        }

        return new string(chars.ToArray());
    }
}
