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 proxy for sensitive OpenClaw agent files, workspace previews,
/// and schema-validated configuration changes.
/// </summary>
[Authorize(Roles = "owner")]
[ApiController]
[Route("api/v1/openclaw")]
public sealed class OpenClawAgentConfigurationController(
    IOpenClawAgentConfigurationService configuration) : ControllerBase
{
    [HttpGet("agents/{agentId}/files")]
    public Task<ActionResult<OpenClawAgentFileCollectionDto>> GetAgentFiles(
        string agentId,
        CancellationToken cancellationToken)
        => ExecuteAsync(() => configuration.GetAgentFilesAsync(agentId, cancellationToken));

    [HttpGet("agents/{agentId}/files/{fileName}")]
    public Task<ActionResult<OpenClawAgentFileDto>> GetAgentFile(
        string agentId,
        string fileName,
        CancellationToken cancellationToken)
        => ExecuteAsync(() => configuration.GetAgentFileAsync(
            agentId,
            fileName,
            cancellationToken));

    [HttpPut("agents/{agentId}/files/{fileName}")]
    [EnableRateLimiting("agents")]
    public async Task<ActionResult<OpenClawAgentFileWriteDto>> SetAgentFile(
        string agentId,
        string fileName,
        [FromBody] UpdateOpenClawAgentFileRequest request,
        CancellationToken cancellationToken)
    {
        if (!TryBuildInvocation(out var invocation, out var validationError))
            return validationError!;

        return await ExecuteAsync(() => configuration.SetAgentFileAsync(
            agentId,
            fileName,
            request,
            invocation!,
            cancellationToken));
    }

    [HttpGet("agents/{agentId}/workspace")]
    public Task<ActionResult<OpenClawWorkspaceCollectionDto>> GetWorkspace(
        string agentId,
        [FromQuery] string? path = null,
        [FromQuery] int offset = 0,
        [FromQuery] int limit = 250,
        CancellationToken cancellationToken = default)
        => ExecuteAsync(() => configuration.GetWorkspaceAsync(
            agentId,
            path,
            offset,
            limit,
            cancellationToken));

    [HttpGet("agents/{agentId}/workspace/file")]
    public Task<ActionResult<OpenClawWorkspaceFileDto>> GetWorkspaceFile(
        string agentId,
        [FromQuery] string path,
        CancellationToken cancellationToken)
        => ExecuteAsync(() => configuration.GetWorkspaceFileAsync(
            agentId,
            path,
            cancellationToken));

    [HttpGet("config/schema")]
    public Task<ActionResult<OpenClawConfigSchemaLookupDto>> GetConfigSchema(
        [FromQuery] string path,
        CancellationToken cancellationToken)
        => ExecuteAsync(() => configuration.GetConfigSchemaAsync(path, cancellationToken));

    [HttpGet("config")]
    public Task<ActionResult<OpenClawConfigSnapshotDto>> GetConfig(
        CancellationToken cancellationToken)
        => ExecuteAsync(() => configuration.GetConfigAsync(cancellationToken));

    [HttpPatch("config")]
    [EnableRateLimiting("agents")]
    public async Task<ActionResult<OpenClawConfigPatchDto>> PatchConfig(
        [FromBody] PatchOpenClawConfigRequest request,
        CancellationToken cancellationToken)
    {
        if (!TryBuildInvocation(out var invocation, out var validationError))
            return validationError!;

        return await ExecuteAsync(() => configuration.PatchConfigAsync(
            request,
            invocation!,
            cancellationToken));
    }

    private async Task<ActionResult<T>> ExecuteAsync<T>(Func<Task<T>> action)
    {
        try
        {
            return Ok(await action());
        }
        catch (OpenClawAgentConfigurationValidationException exception)
        {
            return new BadRequestObjectResult(new ValidationProblemDetails(
                new Dictionary<string, string[]>
                {
                    [exception.Field] = [exception.Message]
                }));
        }
        catch (OpenClawAgentConfigurationConflictException exception)
        {
            return StatusCode(
                StatusCodes.Status409Conflict,
                new
                {
                    code = exception.Code,
                    message = exception.Message,
                    expectedHash = exception.ExpectedHash,
                    currentHash = exception.CurrentHash
                });
        }
        catch (OpenClawAgentConfigurationUnavailableException exception)
        {
            var status = exception.State switch
            {
                "forbidden" or "management_disabled" => StatusCodes.Status403Forbidden,
                "disconnected" => StatusCodes.Status503ServiceUnavailable,
                _ => StatusCodes.Status409Conflict
            };
            return StatusCode(
                status,
                new OpenClawAgentConfigurationErrorDto(
                    exception.State,
                    exception.Message,
                    exception.Method,
                    exception.RequiredScope));
        }
        catch (OpenClawAgentConfigurationVerificationException)
        {
            return StatusCode(
                StatusCodes.Status502BadGateway,
                new OpenClawAgentConfigurationErrorDto(
                    "verification_failed",
                    "OpenClaw-Antwort konnte nicht sicher verifiziert werden."));
        }
        catch (OpenClawGatewayRpcException exception)
        {
            var code = exception.Code.ToUpperInvariant();
            var status = code switch
            {
                "FORBIDDEN" or "AUTH_SCOPE_MISMATCH" => StatusCodes.Status403Forbidden,
                "INVALID_REQUEST" or "BAD_REQUEST" => StatusCodes.Status400BadRequest,
                "METHOD_UNAVAILABLE" or "METHOD_NOT_FOUND" or "NOT_IMPLEMENTED" =>
                    StatusCodes.Status409Conflict,
                "GATEWAY_DISCONNECTED" or "UNAVAILABLE" => StatusCodes.Status503ServiceUnavailable,
                "GATEWAY_TIMEOUT" or "TIMEOUT" => StatusCodes.Status504GatewayTimeout,
                _ when code.StartsWith("AUTH_", StringComparison.Ordinal) ||
                       code.StartsWith("DEVICE_AUTH_", StringComparison.Ordinal) =>
                    StatusCodes.Status403Forbidden,
                _ => StatusCodes.Status502BadGateway
            };
            return StatusCode(
                status,
                new OpenClawAgentConfigurationErrorDto(
                    code.ToLowerInvariant(),
                    SafeGatewayMessage(status)));
        }
    }

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

        var correlationId = Request.Headers["X-Correlation-ID"].FirstOrDefault()?.Trim();
        if (!string.IsNullOrEmpty(correlationId) &&
            (correlationId.Length > 128 || correlationId.Any(char.IsControl)))
        {
            validationError = new BadRequestObjectResult(new ValidationProblemDetails(
                new Dictionary<string, string[]>
                {
                    ["X-Correlation-ID"] =
                    [
                        "X-Correlation-ID must contain at most 128 non-control characters."
                    ]
                }));
            return false;
        }

        var actor = User.FindFirst("sub")?.Value
                    ?? User.FindFirst(ClaimTypes.NameIdentifier)?.Value
                    ?? User.FindFirst(ClaimTypes.Email)?.Value
                    ?? User.Identity?.Name
                    ?? "authenticated-owner";
        var traceParent = Request.Headers["traceparent"].FirstOrDefault()?.Trim();
        try
        {
            invocation = OpenClawInvocationContext.Create(
                actor,
                idempotencyKey,
                correlationId,
                traceParent,
                includeIdempotencyParameter: false);
        }
        catch (ArgumentException exception)
        {
            validationError = new BadRequestObjectResult(new ValidationProblemDetails(
                new Dictionary<string, string[]>
                {
                    ["traceparent"] = [exception.Message]
                }));
            return false;
        }

        Response.Headers["Idempotency-Key"] = invocation.IdempotencyKey;
        Response.Headers["X-Correlation-ID"] = invocation.CorrelationId;
        return true;
    }

    private static string SafeGatewayMessage(int status)
        => status switch
        {
            StatusCodes.Status400BadRequest => "OpenClaw hat die Anfrage als ungültig abgelehnt.",
            StatusCodes.Status403Forbidden => "OpenClaw hat Nexus nicht die erforderliche Berechtigung gewährt.",
            StatusCodes.Status409Conflict => "Die verbundene OpenClaw-Version unterstützt diese Aktion nicht.",
            StatusCodes.Status503ServiceUnavailable => "OpenClaw Gateway ist nicht verfügbar.",
            StatusCodes.Status504GatewayTimeout => "OpenClaw hat nicht rechtzeitig geantwortet.",
            _ => "OpenClaw-Anfrage ist fehlgeschlagen."
        };
}
