using System.Text.Json;

namespace ClawdDotNet.Core.Staging;

/// <summary>Was die Engine mit einem Aufruf tun soll, nachdem das Gate ihn geprüft hat.</summary>
public enum StagingOutcome
{
    /// <summary>Normal ausführen.</summary>
    Proceed,

    /// <summary>Als Vorschlag angelegt — die Ausführung wartet auf Freigabe.</summary>
    Staged,

    /// <summary>Abgelehnt (Policy <c>deny</c>).</summary>
    Denied
}

/// <summary>Ergebnis der Gate-Prüfung samt der Nachricht, die der Agent als Tool-Ergebnis sieht.</summary>
public readonly record struct StagingInterception(StagingOutcome Outcome, string Message, long StagedId)
{
    public static StagingInterception Proceed { get; } = new(StagingOutcome.Proceed, "", 0);
}

/// <summary>
/// Der Durchsetzungspunkt (A2): prüft die Policy für einen konkreten Aufruf und legt bei
/// <c>approve</c> einen eingefrorenen Vorschlag an, statt auszuführen. Optional an der
/// Engine — ohne Gate läuft alles wie bisher.
/// </summary>
public sealed class StagingGate
{
    private readonly StagingPolicy _policy;
    private readonly IStagingRepository _repo;

    public StagingGate(StagingPolicy policy, IStagingRepository repo)
    {
        _policy = policy;
        _repo = repo;
    }

    public async Task<StagingInterception> InterceptAsync(
        string agentId, string instanceId, string runId,
        string tool, string argumentsJson, CancellationToken ct)
    {
        var action = ExtractAction(argumentsJson);

        switch (_policy.Decide(tool, action))
        {
            case StagingDecision.Auto:
                return StagingInterception.Proceed;

            case StagingDecision.Deny:
                return new StagingInterception(
                    StagingOutcome.Denied,
                    $"Aktion '{Label(tool, action)}' ist gesperrt (Policy: deny) und wurde nicht ausgeführt.",
                    0);

            case StagingDecision.Approve:
                var id = await _repo.AppendAsync(new StagedCall
                {
                    RunId = runId,
                    AgentId = agentId,
                    InstanceId = instanceId,
                    Tool = tool,
                    Action = action,
                    ArgumentsJson = argumentsJson,
                    Proposal = BuildProposal(tool, action, argumentsJson)
                }, ct);

                return new StagingInterception(
                    StagingOutcome.Staged,
                    $"Zur Freigabe vorgelegt (#{id}): '{Label(tool, action)}'. " +
                    "Die Aktion wird erst nach menschlicher Freigabe ausgeführt; du wirst danach " +
                    "mit dem Ergebnis geweckt. Fahre mit anderer Arbeit fort oder schließe ab.",
                    id);

            default:
                return StagingInterception.Proceed;
        }
    }

    /// <summary>Liest das <c>action</c>-Argument, wenn vorhanden — der Aktionsschlüssel der Policy.</summary>
    public static string? ExtractAction(string argumentsJson)
    {
        if (string.IsNullOrWhiteSpace(argumentsJson))
            return null;
        try
        {
            using var doc = JsonDocument.Parse(argumentsJson);
            return doc.RootElement.ValueKind == JsonValueKind.Object
                && doc.RootElement.TryGetProperty("action", out var a)
                && a.ValueKind == JsonValueKind.String
                ? a.GetString()
                : null;
        }
        catch (JsonException)
        {
            return null;
        }
    }

    private static string Label(string tool, string? action)
        => string.IsNullOrWhiteSpace(action) ? tool : $"{tool}.{action}";

    private static string BuildProposal(string tool, string? action, string argumentsJson)
    {
        var args = argumentsJson.Length > 500 ? argumentsJson[..500] + "…" : argumentsJson;
        return $"{Label(tool, action)} {args}".Trim();
    }
}

/// <summary>
/// Führt einen freigegebenen, eingefrorenen Aufruf aus — mit gültigem Tool-Kontext, aber
/// ohne LLM-Schleife. Von der Engine implementiert.
/// </summary>
public interface IFrozenCallExecutor
{
    Task<string> ExecuteApprovedCallAsync(
        string agentId, string tool, string argumentsJson, string runId, CancellationToken ct);
}
