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;

[Authorize]
[ApiController]
[Route("api/v1/openclaw/runs")]
public sealed class OpenClawRunsController(IOpenClawRunService runs) : ControllerBase
{
    [HttpGet]
    public async Task<ActionResult<OpenClawRunCollectionDto>> Get(
        [FromQuery] int limit = 50,
        [FromQuery] string? cursor = null,
        [FromQuery] string? status = null,
        [FromQuery] Guid? taskId = null,
        [FromQuery] Guid? projectId = null,
        [FromQuery] string? sessionKey = null,
        CancellationToken cancellationToken = default)
        => Ok(await runs.GetAsync(
            new OpenClawRunQuery(limit, cursor, status, taskId, projectId, sessionKey),
            cancellationToken));

    [HttpGet("{id:guid}", Name = "GetOpenClawRun")]
    public async Task<ActionResult<OpenClawRunDto>> GetById(
        Guid id,
        CancellationToken cancellationToken)
    {
        var run = await runs.GetByIdAsync(id, cancellationToken);
        return run is null ? NotFound() : Ok(run);
    }

    [HttpGet("{id:guid}/history")]
    public async Task<ActionResult<OpenClawRunHistoryResponse>> GetHistory(
        Guid id,
        [FromQuery] int gatewayLimit = 200,
        CancellationToken cancellationToken = default)
    {
        var result = await runs.GetHistoryAsync(
            id,
            Math.Clamp(gatewayLimit, 1, 1000),
            cancellationToken);
        return result is null ? NotFound() : Ok(result);
    }

    [HttpPost]
    [Authorize(Roles = "owner")]
    [EnableRateLimiting("agents")]
    public async Task<ActionResult<OpenClawRunOperationDto>> Start(
        [FromBody] StartOpenClawRunRequest request,
        CancellationToken cancellationToken)
    {
        var validation = ValidateStart(request);
        if (validation is not null)
            return validation;
        if (!TryBuildInvocation(out var invocation, out var metadataError))
            return metadataError!;

        try
        {
            var result = await runs.StartAsync(request, invocation!, cancellationToken);
            if (result.Ok)
            {
                return CreatedAtRoute(
                    "GetOpenClawRun",
                    new { id = result.Run.Id },
                    result);
            }

            if (result.State != "idempotency_conflict")
            {
                return AcceptedAtRoute(
                    "GetOpenClawRun",
                    new { id = result.Run.Id },
                    result);
            }

            return StatusCode(StatusFor(result.State), result);
        }
        catch (OpenClawRunValidationException exception)
        {
            return new BadRequestObjectResult(new ValidationProblemDetails(
                new Dictionary<string, string[]>
                {
                    [exception.Field] = [exception.Message]
                }));
        }
    }

    [HttpPost("{id:guid}/stop")]
    [Authorize(Roles = "owner")]
    [EnableRateLimiting("agents")]
    public async Task<ActionResult<OpenClawRunOperationDto>> Stop(
        Guid id,
        [FromBody] OpenClawRunActionRequest? request,
        CancellationToken cancellationToken)
    {
        var reasonError = ValidateReason(request?.Reason);
        if (reasonError is not null)
            return reasonError;
        if (!TryBuildInvocation(out var invocation, out var metadataError))
            return metadataError!;
        var result = await runs.StopAsync(
            id,
            request?.Reason,
            invocation!,
            cancellationToken);
        return MapOperation(result);
    }

    [HttpPost("{id:guid}/resume")]
    [Authorize(Roles = "owner")]
    [EnableRateLimiting("agents")]
    public async Task<ActionResult<OpenClawRunOperationDto>> Resume(
        Guid id,
        [FromBody] OpenClawRunActionRequest? request,
        CancellationToken cancellationToken)
    {
        var reasonError = ValidateReason(request?.Reason);
        if (reasonError is not null)
            return reasonError;
        if (!TryBuildInvocation(out var invocation, out var metadataError))
            return metadataError!;
        var result = await runs.ResumeAsync(
            id,
            request?.Reason,
            invocation!,
            cancellationToken);
        return MapOperation(result);
    }

    [HttpPost("{id:guid}/retry")]
    [Authorize(Roles = "owner")]
    [EnableRateLimiting("agents")]
    public async Task<ActionResult<OpenClawRunOperationDto>> Retry(
        Guid id,
        [FromBody] OpenClawRunActionRequest? request,
        CancellationToken cancellationToken)
    {
        var reasonError = ValidateReason(request?.Reason);
        if (reasonError is not null)
            return reasonError;
        if (!TryBuildInvocation(out var invocation, out var metadataError))
            return metadataError!;
        var result = await runs.RetryAsync(
            id,
            request?.Reason,
            invocation!,
            cancellationToken);
        if (result is null)
            return NotFound();
        if (result.ResultRun is not null)
        {
            return result.Ok
                ? CreatedAtRoute(
                    "GetOpenClawRun",
                    new { id = result.ResultRun.Id },
                    result)
                : AcceptedAtRoute(
                    "GetOpenClawRun",
                    new { id = result.ResultRun.Id },
                    result);
        }

        return StatusCode(StatusFor(result.State), result);
    }

    private ActionResult<OpenClawRunOperationDto> MapOperation(OpenClawRunOperationDto? result)
    {
        if (result is null)
            return NotFound();
        return result.Ok
            ? Ok(result)
            : StatusCode(StatusFor(result.State), result);
    }

    private bool TryBuildInvocation(
        out OpenClawInvocationMetadata? invocation,
        out ActionResult<OpenClawRunOperationDto>? 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-user";
        Response.Headers["X-Correlation-ID"] = correlationId;
        invocation = new OpenClawInvocationMetadata(
            idempotencyKey,
            correlationId,
            actor,
            traceParent);
        return true;
    }

    private static ActionResult<OpenClawRunOperationDto>? ValidateStart(
        StartOpenClawRunRequest request)
    {
        var errors = new Dictionary<string, string[]>();
        if (string.IsNullOrWhiteSpace(request.Prompt))
            errors["prompt"] = ["Prompt is required."];
        else if (request.Prompt.Length > 50_000)
            errors["prompt"] = ["Prompt must contain at most 50000 characters."];
        if (string.IsNullOrWhiteSpace(request.AgentId))
            errors["agentId"] = ["Agent id is required."];
        else if (request.AgentId.Length > 200)
            errors["agentId"] = ["Agent id must contain at most 200 characters."];
        if (string.IsNullOrWhiteSpace(request.SessionKey))
            errors["sessionKey"] = ["Session key is required."];
        else if (request.SessionKey.Length > 500)
            errors["sessionKey"] = ["Session key must contain at most 500 characters."];
        if (request.Title?.Length > 160)
            errors["title"] = ["Title must contain at most 160 characters."];

        if (errors.Count == 0)
            return null;

        return new ActionResult<OpenClawRunOperationDto>(
            new BadRequestObjectResult(new ValidationProblemDetails(errors)));
    }

    private static ActionResult<OpenClawRunOperationDto>? ValidateReason(string? reason)
    {
        if (reason?.Length is not > 1000)
            return null;

        return new ActionResult<OpenClawRunOperationDto>(
            new BadRequestObjectResult(new ValidationProblemDetails(
                new Dictionary<string, string[]>
                {
                    ["reason"] = ["Reason must contain at most 1000 characters."]
                })));
    }

    private static int StatusFor(string state)
        => state switch
        {
            "idempotency_conflict" or "invalid_state" or "unsupported" => StatusCodes.Status409Conflict,
            "blocked" => StatusCodes.Status503ServiceUnavailable,
            _ => StatusCodes.Status502BadGateway
        };
}
