using System.Text.Json;
using System.Text.Json.Nodes;
using Nexus.Api.Data;
using Nexus.Api.Models;
using Nexus.Api.Services;
using Xunit;

namespace Nexus.Api.Tests;

public sealed class OpenClawRunGatewayTests
{
    [Fact]
    public async Task Start_UsesOfficialChatSendShape_AndForwardsInvocationContext()
    {
        var connector = new CapturingConnector(
            ["chat.send"],
            new JsonObject
            {
                ["runId"] = "oc-run-42",
                ["status"] = "started"
            });
        var gateway = new OpenClawRunGateway(
            connector,
            new StubOpenClawWriteGate());
        var invocation = Invocation();

        var result = await gateway.StartAsync(Run(), invocation);

        Assert.True(result.Ok);
        Assert.Equal("oc-run-42", result.OpenClawRunId);
        var call = Assert.Single(connector.Calls);
        Assert.Equal("chat.send", call.Method);
        Assert.Equal("agent:iris:main", call.Parameters?["sessionKey"]?.GetValue<string>());
        Assert.Equal("iris", call.Parameters?["agentId"]?.GetValue<string>());
        Assert.Equal("Do the work", call.Parameters?["message"]?.GetValue<string>());
        Assert.False(call.Parameters?["deliver"]?.GetValue<bool>());
        Assert.Equal("run-idempotency", call.Parameters?["idempotencyKey"]?.GetValue<string>());
        Assert.Equal("run-idempotency", call.Invocation?.IdempotencyKey);
        Assert.Equal("run-correlation", call.Invocation?.CorrelationId);
        Assert.Equal("owner-subject", call.Invocation?.Actor);
        Assert.True(call.Invocation?.IncludeIdempotencyParameter);
    }

    [Fact]
    public async Task Start_DoesNotInvokeGateway_WhenWriteBoundaryIsBlocked()
    {
        var connector = new CapturingConnector(
            ["chat.send"],
            new JsonObject
            {
                ["runId"] = "must-not-be-used",
                ["status"] = "started"
            });
        var gate = new StubOpenClawWriteGate(
            OpenClawWriteGateDecision.Block(
                "endpoint_trust_mismatch",
                "The active Gateway endpoint no longer matches the adopted profile.",
                "Verify and adopt the connection again."));
        var gateway = new OpenClawRunGateway(connector, gate);

        var result = await gateway.StartAsync(Run(), Invocation());

        Assert.False(result.Ok);
        Assert.Equal(OpenClawRunStates.Blocked, result.State);
        Assert.Empty(connector.Calls);
        Assert.Equal(("chat.send", "operator.write"), Assert.Single(gate.Evaluations));
    }

    [Fact]
    public async Task Stop_RefusesSessionWideAbortWithoutExactOpenClawRunId()
    {
        var connector = new CapturingConnector(["chat.abort"], new JsonObject());
        var gateway = new OpenClawRunGateway(
            connector,
            new StubOpenClawWriteGate());
        var run = Run();
        run.OpenClawRunId = null;

        var result = await gateway.StopAsync(run, Invocation());

        Assert.False(result.Ok);
        Assert.Equal(OpenClawRunStates.Blocked, result.State);
        Assert.Empty(connector.Calls);
    }

    [Fact]
    public async Task Stop_UsesExactChatAbort_WithoutUnsupportedWireFields()
    {
        var connector = new CapturingConnector(["chat.abort"], new JsonObject { ["ok"] = true });
        var gateway = new OpenClawRunGateway(
            connector,
            new StubOpenClawWriteGate());

        var result = await gateway.StopAsync(Run(), Invocation());

        Assert.True(result.Ok);
        var call = Assert.Single(connector.Calls);
        Assert.Equal("chat.abort", call.Method);
        Assert.Equal("oc-run-1", call.Parameters?["runId"]?.GetValue<string>());
        Assert.Null(call.Parameters?["idempotencyKey"]);
        Assert.False(call.Invocation?.IncludeIdempotencyParameter);
    }

    [Fact]
    public async Task History_IsDisplayNormalizedGatewayData_WithSecretsRedacted()
    {
        var connector = new CapturingConnector(
            ["chat.history"],
            new JsonObject
            {
                ["messages"] = new JsonArray(new JsonObject { ["text"] = "done" }),
                ["accessToken"] = "secret-value",
                ["inputTokens"] = 12
            });
        var gateway = new OpenClawRunGateway(
            connector,
            new StubOpenClawWriteGate());

        var result = await gateway.GetHistoryAsync(Run(), 50);

        Assert.True(result.Ok);
        Assert.Equal("[redacted]", result.Data?["accessToken"]?.GetValue<string>());
        Assert.Equal(12, result.Data?["inputTokens"]?.GetValue<int>());
        var call = Assert.Single(connector.Calls);
        Assert.Equal("chat.history", call.Method);
        Assert.Equal(50, call.Parameters?["limit"]?.GetValue<int>());
        Assert.Null(call.Invocation);
    }

    private static OpenClawRun Run()
        => new()
        {
            Title = "Run",
            Prompt = "Do the work",
            AgentId = "iris",
            SessionKey = "agent:iris:main",
            Status = OpenClawRunStates.Running,
            OpenClawRunId = "oc-run-1",
            StartIdempotencyKey = "run-idempotency",
            CorrelationId = "run-correlation",
            Actor = "owner-subject"
        };

    private static OpenClawInvocationMetadata Invocation()
        => new(
            "run-idempotency",
            "run-correlation",
            "owner-subject",
            "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01");

    private sealed class CapturingConnector : IGatewayConnector
    {
        private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
        private readonly JsonNode? _response;

        public CapturingConnector(IEnumerable<string> methods, JsonNode? response)
        {
            AdvertisedMethods = new HashSet<string>(methods, StringComparer.Ordinal);
            _response = response;
        }

        public List<InvocationCall> Calls { get; } = [];
        public GatewayConnectionState ConnectionState => GatewayConnectionState.Connected;
        public string? GatewayVersion => "2026.7.0";
        public string? RequiredVersion => "2026.7.0";
        public DateTimeOffset? LastConnectedAt => DateTimeOffset.UtcNow;
        public int ReconnectAttempts => 0;
        public string? StatusMessage => "Connected";
        public string? DeviceId => "nexus-tests";
        public bool DeviceTokenConfigured => true;
        public bool PairingRequired => false;
        public string? PairingRequestId => null;
        public int? ProtocolVersion => 4;
        public IReadOnlySet<string> AdvertisedMethods { get; }
        public IReadOnlySet<string> AdvertisedEvents { get; } = new HashSet<string>();
        public IReadOnlySet<string> GrantedScopes { get; } = new HashSet<string>();
        public DateTimeOffset? LastEventAt => null;

        public bool Supports(string method) => AdvertisedMethods.Contains(method);

        public Task<JsonNode?> InvokeAsync(
            string method,
            object? parameters = null,
            TimeSpan? timeout = null,
            CancellationToken cancellationToken = default,
            OpenClawInvocationContext? invocationContext = null)
        {
            var node = parameters switch
            {
                null => null,
                JsonNode jsonNode => jsonNode.DeepClone(),
                _ => JsonSerializer.SerializeToNode(parameters, JsonOptions)
            };
            Calls.Add(new InvocationCall(method, node, invocationContext));
            return Task.FromResult(_response?.DeepClone());
        }

        public IReadOnlyList<GatewayEventEnvelope> GetRecentEvents(int limit = 100) => [];
    }

    private sealed record InvocationCall(
        string Method,
        JsonNode? Parameters,
        OpenClawInvocationContext? Invocation);
}
