using System.Diagnostics;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Nexus.Api.Models;
using Nexus.Api.Services;

namespace Nexus.Api.Controllers;

/// <summary>
/// Owner-only approval boundary for OpenClaw agent creation. Proposal creation
/// never mutates OpenClaw; approval queues a durable request only after every
/// live management gate passes.
/// </summary>
[Authorize(Roles = "owner")]
[ApiController]
[Route("api/v1/openclaw/agent-proposals")]
public sealed class OpenClawAgentProposalsController(
    IAgentProposalService proposals) : ControllerBase
{
    [HttpGet("~/api/v1/openclaw/agents/create-options")]
    public async Task<ActionResult<AgentCreateOptionsDto>> GetCreateOptions(
        CancellationToken cancellationToken)
        => Ok(await proposals.GetCreateOptionsAsync(cancellationToken));

    [HttpGet]
    public async Task<ActionResult<AgentProposalCollectionDto>> Get(
        [FromQuery] int limit = 50,
        [FromQuery] string? cursor = null,
        [FromQuery] string? status = null,
        CancellationToken cancellationToken = default)
    {
        try
        {
            return Ok(await proposals.GetAsync(
                limit,
                cursor,
                status,
                cancellationToken));
        }
        catch (AgentProposalValidationException exception)
        {
            return Validation(exception);
        }
    }

    [HttpGet("{id:guid}", Name = "GetAgentProposal")]
    public async Task<ActionResult<AgentProposalDto>> GetById(
        Guid id,
        CancellationToken cancellationToken)
    {
        var proposal = await proposals.GetByIdAsync(
            id,
            includeFileContent: true,
            cancellationToken);
        return proposal is null ? NotFound() : Ok(proposal);
    }

    [HttpPost]
    [EnableRateLimiting("agents")]
    public async Task<ActionResult<AgentProposalOperationDto>> Create(
        [FromBody] CreateAgentProposalRequest request,
        CancellationToken cancellationToken)
    {
        if (!TryBuildInvocation(out var invocation, out var error))
            return error!;
        try
        {
            var result = await proposals.CreateAsync(
                request,
                "manual",
                invocation!,
                cancellationToken);
            return result.Ok
                ? CreatedAtRoute(
                    "GetAgentProposal",
                    new { id = result.Proposal!.Id },
                    result)
                : StatusCode(StatusFor(result.State), result);
        }
        catch (AgentProposalValidationException exception)
        {
            return Validation(exception);
        }
    }

    [HttpPost("{id:guid}/approve")]
    [EnableRateLimiting("agents")]
    public Task<ActionResult<AgentProposalOperationDto>> Approve(
        Guid id,
        [FromBody] AgentProposalActionRequest request,
        CancellationToken cancellationToken)
        => Mutate(
            id,
            request,
            proposals.ApproveAsync,
            acceptedWhenProvisioning: true,
            cancellationToken);

    [HttpPost("{id:guid}/reject")]
    [EnableRateLimiting("agents")]
    public Task<ActionResult<AgentProposalOperationDto>> Reject(
        Guid id,
        [FromBody] AgentProposalActionRequest request,
        CancellationToken cancellationToken)
        => Mutate(
            id,
            request,
            proposals.RejectAsync,
            acceptedWhenProvisioning: false,
            cancellationToken);

    [HttpPost("{id:guid}/retry")]
    [EnableRateLimiting("agents")]
    public Task<ActionResult<AgentProposalOperationDto>> Retry(
        Guid id,
        [FromBody] AgentProposalActionRequest request,
        CancellationToken cancellationToken)
        => Mutate(
            id,
            request,
            proposals.RetryAsync,
            acceptedWhenProvisioning: true,
            cancellationToken);

    private async Task<ActionResult<AgentProposalOperationDto>> Mutate(
        Guid id,
        AgentProposalActionRequest request,
        Func<
            Guid,
            AgentProposalActionRequest,
            OpenClawInvocationMetadata,
            CancellationToken,
            Task<AgentProposalOperationDto>> operation,
        bool acceptedWhenProvisioning,
        CancellationToken cancellationToken)
    {
        if (!TryBuildInvocation(out var invocation, out var error))
            return error!;
        try
        {
            var result = await operation(
                id,
                request,
                invocation!,
                cancellationToken);
            if (!result.Ok)
                return StatusCode(StatusFor(result.State), result);
            if (acceptedWhenProvisioning && result.State == "provisioning")
            {
                return AcceptedAtRoute(
                    "GetAgentProposal",
                    new { id = result.Proposal!.Id },
                    result);
            }

            return Ok(result);
        }
        catch (AgentProposalValidationException exception)
        {
            return Validation(exception);
        }
    }

    private bool TryBuildInvocation(
        out OpenClawInvocationMetadata? invocation,
        out ActionResult<AgentProposalOperationDto>? error)
    {
        invocation = null;
        error = null;
        var idempotencyKey = Request.Headers["Idempotency-Key"]
            .FirstOrDefault()
            ?.Trim();
        if (string.IsNullOrWhiteSpace(idempotencyKey)
            || idempotencyKey.Length > 200)
        {
            error = new BadRequestObjectResult(new ValidationProblemDetails(
                new Dictionary<string, string[]>
                {
                    ["Idempotency-Key"] =
                    [
                        "A non-empty Idempotency-Key header with at most 200 characters is required."
                    ]
                }));
            return false;
        }

        var correlationId = Request.Headers["X-Correlation-ID"]
            .FirstOrDefault()
            ?.Trim();
        if (string.IsNullOrWhiteSpace(correlationId))
            correlationId = HttpContext.TraceIdentifier;
        if (correlationId.Length > 200)
        {
            error = new BadRequestObjectResult(new ValidationProblemDetails(
                new Dictionary<string, string[]>
                {
                    ["X-Correlation-ID"] =
                    [
                        "X-Correlation-ID must contain at most 200 characters."
                    ]
                }));
            return false;
        }

        var traceParent = Request.Headers["traceparent"].FirstOrDefault()?.Trim()
                          ?? Activity.Current?.Id;
        if (traceParent?.Length > 128
            || (!string.IsNullOrWhiteSpace(traceParent)
                && !ActivityContext.TryParse(traceParent, null, out _)))
        {
            error = new BadRequestObjectResult(new ValidationProblemDetails(
                new Dictionary<string, string[]>
                {
                    ["traceparent"] =
                    [
                        "traceparent must be a valid W3C trace context with at most 128 characters."
                    ]
                }));
            return false;
        }

        var actor = User.FindFirst("sub")?.Value
                    ?? User.FindFirst(ClaimTypes.NameIdentifier)?.Value
                    ?? User.FindFirst(ClaimTypes.Email)?.Value
                    ?? User.Identity?.Name
                    ?? "authenticated-owner";
        Response.Headers["X-Correlation-ID"] = correlationId;
        invocation = new OpenClawInvocationMetadata(
            idempotencyKey,
            correlationId,
            actor,
            traceParent);
        return true;
    }

    private BadRequestObjectResult Validation(
        AgentProposalValidationException exception)
        => new BadRequestObjectResult(new ValidationProblemDetails(
            new Dictionary<string, string[]>
            {
                [exception.Field] = [exception.Message]
            }));

    private static int StatusFor(string state)
        => state switch
        {
            "invalid"
                or "invalid_state"
                or "invalid_stage"
                => StatusCodes.Status400BadRequest,
            "not_found"
                => StatusCodes.Status404NotFound,
            "concurrency_conflict"
                or "idempotency_conflict"
                or "agent_exists"
                => StatusCodes.Status409Conflict,
            "experimental_blocked"
                or "management_disabled"
                or "scope_upgrade_required"
                or "capability_missing"
                or "capability_drift"
                or "version_mismatch"
                or "workspace_root_invalid"
                => StatusCodes.Status403Forbidden,
            "gateway_unavailable"
                or "inventory_unavailable"
                => StatusCodes.Status503ServiceUnavailable,
            _ => StatusCodes.Status502BadGateway
        };
}
