using System.Text.Json.Nodes;
using Nexus.Api.Models;

namespace Nexus.Api.Services;

/// <summary>
/// Maintains a persistent WebSocket connection to the OpenClaw Gateway
/// for real-time event streaming and health monitoring.
/// </summary>
public interface IGatewayConnector
{
    /// <summary>
    /// Current WebSocket connection state.
    /// </summary>
    GatewayConnectionState ConnectionState { get; }

    /// <summary>
    /// Gateway version reported at last connection.
    /// </summary>
    string? GatewayVersion { get; }

    /// <summary>
    /// Required version from configuration, falling back to Nexus' verified
    /// OpenClaw release pin when the setting is absent or blank.
    /// </summary>
    string? RequiredVersion { get; }

    /// <summary>
    /// Timestamp of the last successful connection attempt.
    /// </summary>
    DateTimeOffset? LastConnectedAt { get; }

    /// <summary>
    /// Number of consecutive failed connection attempts.
    /// </summary>
    int ReconnectAttempts { get; }

    /// <summary>
    /// Detailed status message (e.g. error or version info).
    /// </summary>
    string? StatusMessage { get; }

    /// <summary>
    /// Stable Nexus backend device id used for non-loopback Gateway pairing.
    /// The private key and device token are never exposed through this contract.
    /// </summary>
    string? DeviceId { get; }

    /// <summary>
    /// Whether a paired backend device token is available in protected server-side storage.
    /// </summary>
    bool DeviceTokenConfigured { get; }

    /// <summary>
    /// Whether the Gateway is waiting for an operator to approve the current device request.
    /// </summary>
    bool PairingRequired { get; }

    /// <summary>
    /// Exact pending OpenClaw pairing request id, when supplied by the Gateway.
    /// </summary>
    string? PairingRequestId { get; }

    /// <summary>
    /// Negotiated Gateway protocol version from the latest hello-ok frame.
    /// </summary>
    int? ProtocolVersion { get; }

    /// <summary>
    /// RPC methods advertised by the connected Gateway.
    /// </summary>
    IReadOnlySet<string> AdvertisedMethods { get; }

    /// <summary>
    /// Event families advertised by the connected Gateway.
    /// </summary>
    IReadOnlySet<string> AdvertisedEvents { get; }

    /// <summary>
    /// Operator scopes granted by the Gateway during the handshake.
    /// </summary>
    IReadOnlySet<string> GrantedScopes { get; }

    /// <summary>
    /// Normalized endpoint and TLS pin currently used by the running connector.
    /// These values never include credentials.
    /// </summary>
    string? ActiveEndpoint => null;
    string? ActiveTlsFingerprint => null;

    /// <summary>
    /// Timestamp of the latest event frame received from the Gateway.
    /// </summary>
    DateTimeOffset? LastEventAt { get; }

    /// <summary>
    /// Returns whether the current Gateway explicitly advertises an RPC method.
    /// </summary>
    bool Supports(string method);

    /// <summary>
    /// Invokes a Gateway RPC over the authenticated protocol-v4 connection.
    /// </summary>
    Task<JsonNode?> InvokeAsync(
        string method,
        object? parameters = null,
        TimeSpan? timeout = null,
        CancellationToken cancellationToken = default,
        OpenClawInvocationContext? invocationContext = null);

    /// <summary>
    /// Returns a bounded newest-first snapshot of recently received Gateway events.
    /// </summary>
    IReadOnlyList<GatewayEventEnvelope> GetRecentEvents(int limit = 100);

    /// <summary>
    /// Requests a new explicit operator-scope set for the next Gateway
    /// handshake. The production connector closes the current socket so
    /// OpenClaw can start its normal pairing or scope-upgrade flow.
    /// Test connectors may keep the default no-op implementation.
    /// </summary>
    Task RequestOperatorScopesAsync(
        IReadOnlyCollection<string> scopes,
        CancellationToken cancellationToken = default)
        => Task.CompletedTask;

    /// <summary>
    /// Re-targets the single connector after Setup has validated the endpoint.
    /// The optional bootstrap token remains process-memory-only until OpenClaw
    /// issues a bound device token.
    /// </summary>
    Task ConfigureEndpointAsync(
        string endpoint,
        string? tlsFingerprint,
        string? bootstrapToken,
        CancellationToken cancellationToken = default)
        => Task.CompletedTask;

    /// <summary>
    /// Closes the active socket after a local detach. The background connector
    /// may return to an unauthenticated discovery/pairing state, but the
    /// adopted profile and protected device token are no longer active.
    /// </summary>
    Task DisconnectAsync(CancellationToken cancellationToken = default)
        => Task.CompletedTask;
}

public sealed record GatewayEventEnvelope(
    string Event,
    JsonNode? Payload,
    long? Sequence,
    long? StateVersion,
    DateTimeOffset ReceivedAt);

/// <summary>
/// Correlation metadata for one logical Nexus-to-OpenClaw invocation.
/// Only fields defined by the OpenClaw wire schema are sent to the Gateway:
/// correlation is reflected in the request id, W3C traceparent is attached to
/// the request frame, and idempotencyKey is added to params only when
/// <see cref="IncludeIdempotencyParameter"/> is explicitly enabled for a
/// schema-confirmed method. Actor and the complete context remain in Nexus'
/// local audit boundary.
/// </summary>
public sealed record OpenClawInvocationContext(
    string IdempotencyKey,
    string CorrelationId,
    string Actor,
    string TraceParent,
    bool IncludeIdempotencyParameter = false)
{
    public static OpenClawInvocationContext Create(
        string? actor = null,
        string? idempotencyKey = null,
        string? correlationId = null,
        string? traceParent = null,
        bool includeIdempotencyParameter = false)
        => OpenClawInvocationContextFactory.Create(
            actor,
            idempotencyKey,
            correlationId,
            traceParent,
            includeIdempotencyParameter);
}

public sealed class OpenClawGatewayRpcException : Exception
{
    public OpenClawGatewayRpcException(
        string code,
        string message,
        JsonNode? details = null,
        bool retryable = false,
        int? retryAfterMs = null)
        : base(message)
    {
        Code = code;
        Details = details;
        Retryable = retryable;
        RetryAfterMs = retryAfterMs;
    }

    public string Code { get; }
    public JsonNode? Details { get; }
    public bool Retryable { get; }
    public int? RetryAfterMs { get; }
}

/// <summary>
/// Represents the current connection state of the GatewayConnector.
/// </summary>
public enum GatewayConnectionState
{
    /// <summary>Not yet attempted or initializing.</summary>
    Initializing,
    /// <summary>Connected and healthy.</summary>
    Connected,
    /// <summary>Disconnected, waiting for reconnect.</summary>
    Disconnected,
    /// <summary>Recoverable error, retrying.</summary>
    Reconnecting,
    /// <summary>Failed after maximum retries.</summary>
    Failed
}
