using System.Diagnostics;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace Dada.Supervisor;

internal static partial class ControlledExternalValidationLauncher
{
    private static readonly HashSet<string> AllowedModels =
    [
        "gemini-3.1-flash-image",
        "gpt-image-2",
    ];

    private static readonly HashSet<string> ValueOptions =
    [
        "--max-real-calls",
        "--model",
        "--run-id",
        "--service",
    ];

    private static readonly HashSet<string> SwitchOptions =
    [
        "--confirm-controlled-real",
        "--execute-controlled-real",
    ];

    internal static async Task<int> RunAsync(string[] args, ICredentialStore credentials, CancellationToken cancellationToken = default)
    {
        var validated = ValidateArguments(args);
        var script = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "scripts", "validate-external.mjs"));
        if (!File.Exists(script)) throw new InvalidOperationException("external_validator_not_found");
        var startInfo = new ProcessStartInfo("node") { WorkingDirectory = Environment.CurrentDirectory };
        startInfo.ArgumentList.Add(script);
        foreach (var value in validated) startInfo.ArgumentList.Add(value);
        startInfo.ArgumentList.Add("--credential-stdin");

        var result = await CredentialProcessLauncher.RunToCompletionAsync(startInfo, ChildRole.Worker, credentials, cancellationToken);
        if (result.SensitiveOutputDetected || !TrySelectSanitizedJson(result, out var output, out var useError))
        {
            Console.Error.WriteLine("{\"code\":\"external_validator_output_invalid\",\"real_calls\":0,\"status\":\"failed\"}");
            return 1;
        }
        if (useError) Console.Error.WriteLine(output); else Console.WriteLine(output);
        return result.ExitCode;
    }

    internal static string[] ValidateArguments(string[] args)
    {
        var values = new Dictionary<string, string>(StringComparer.Ordinal);
        var switches = new HashSet<string>(StringComparer.Ordinal);
        for (var index = 0; index < args.Length; index++)
        {
            var option = args[index];
            if (SwitchOptions.Contains(option))
            {
                if (!switches.Add(option)) throw new ArgumentException("external_validator_argument_duplicate");
                continue;
            }
            if (!ValueOptions.Contains(option) || index + 1 >= args.Length || !values.TryAdd(option, args[++index]))
            {
                throw new ArgumentException("external_validator_argument_invalid");
            }
        }
        if (values.GetValueOrDefault("--service") != "ai-gateway-service-id"
            || !AllowedModels.Contains(values.GetValueOrDefault("--model") ?? string.Empty)
            || !SafeRunId().IsMatch(values.GetValueOrDefault("--run-id") ?? string.Empty)
            || values.GetValueOrDefault("--max-real-calls") != "120"
            || !switches.SetEquals(SwitchOptions))
        {
            throw new ArgumentException("external_validator_argument_invalid");
        }
        if (values.Values.Any(value => value.Length == 0 || value.IndexOfAny(['\r', '\n', '\0']) >= 0))
        {
            throw new ArgumentException("external_validator_argument_invalid");
        }
        return args.ToArray();
    }

    private static bool TrySelectSanitizedJson(CredentialProcessResult result, out string output, out bool useError)
    {
        var stdout = result.StandardOutput.Trim();
        var stderr = result.StandardError.Trim();
        useError = stdout.Length == 0;
        output = useError ? stderr : stdout;
        if (output.Length == 0 || (stdout.Length > 0 && stderr.Length > 0)) return false;
        try
        {
            using var document = JsonDocument.Parse(output);
            return IsSanitized(document.RootElement);
        }
        catch (JsonException)
        {
            return false;
        }
    }

    private static bool IsSanitized(JsonElement element)
    {
        if (element.ValueKind == JsonValueKind.Object)
        {
            foreach (var property in element.EnumerateObject())
            {
                if (ForbiddenKey().IsMatch(property.Name) || property.NameEquals("verified") || !IsSanitized(property.Value)) return false;
            }
        }
        else if (element.ValueKind == JsonValueKind.Array)
        {
            foreach (var item in element.EnumerateArray()) if (!IsSanitized(item)) return false;
        }
        else if (element.ValueKind == JsonValueKind.String)
        {
            var value = element.GetString() ?? string.Empty;
            if (WindowsUserPath().IsMatch(value) || BearerValue().IsMatch(value)) return false;
        }
        return true;
    }

    [GeneratedRegex("^[A-Za-z0-9][A-Za-z0-9._-]{0,119}$", RegexOptions.CultureInvariant)]
    private static partial Regex SafeRunId();

    [GeneratedRegex("(?:^|_)(?:absolute_path|authorization|body|credential|image|password|path|prompt|raw|secret|token)(?:_|$)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
    private static partial Regex ForbiddenKey();

    [GeneratedRegex("[A-Za-z]:\\\\Users\\\\", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
    private static partial Regex WindowsUserPath();

    [GeneratedRegex("(?:Bearer\\s+|\\bsk-[A-Za-z0-9_-]{8,})", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
    private static partial Regex BearerValue();
}
