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

namespace Nexus.Api.Controllers;

/// <summary>
/// MCP-style (structured-command) backend bridge for agent-facing operations.
///
/// This is the SINGLE entrypoint for agents (Iris + sub-agents) to interact with
/// the Nexus task board, activity log, and delegation workflow.
///
/// AUTHENTICATION: Requires a verified JWT or X-Nexus-Api-Key. X-Agent-Id is
/// accepted only as an actor hint for a service or privileged user principal.
/// The browser NEVER uses this controller — only backend-to-backend and gateway-to-backend.
///
/// DESIGN PRINCIPLE: No MCP protocol between Nexus and Gateway — instead, the Gateway
/// calls these structured HTTP endpoints (same pattern, simpler transport).
///
/// COMMANDS:
///   create_task       → POST /api/bridge/tasks
///   create_child_task → POST /api/bridge/tasks/{id}/children
///   update_status     → PATCH /api/bridge/tasks/{id}/status
///   append_activity   → POST /api/bridge/tasks/{id}/activity
///   handoff           → POST /api/bridge/tasks/{id}/handoff
///   get_board         → GET  /api/bridge/board
///   get_task          → GET  /api/bridge/tasks/{id}
///   get_children      → GET  /api/bridge/tasks/{id}/children
///   get_activity      → GET  /api/bridge/tasks/{id}/activity
///   get_agent_overview → GET /api/bridge/agent-overview
/// </summary>
[ApiController]
[Route("api/bridge")]
[Authorize]
[EnableRateLimiting("agents")]
public class GatewayBridgeController(
    ITaskBridgeService bridge,
    IAgentService agentService,
    IConfiguration configuration,
    ILogger<GatewayBridgeController> logger) : ControllerBase
{
    private const string ApikeyErrorMessage =
        "Bridge endpoints require a verified JWT or X-Nexus-Api-Key.";

    [HttpGet("health")]
    public IResult Health()
    {
        return Results.Ok(new
        {
            status = "ok",
            service = "nexus-bridge",
            version = "1.0.0",
            commands = new[]
            {
                "create_task", "create_child_task", "update_status",
                "append_activity", "handoff", "get_board", "get_task",
                "get_children", "get_activity", "get_agent_overview"
            }
        });
    }

    [HttpPost("tasks")]
    public async Task<ActionResult<TaskBridgeCommandResponse<DashboardTaskDto>>> CreateTask(
        [FromBody] BridgeCreateTaskCommand command,
        CancellationToken ct)
    {
        var resolution = await TryResolveAgentAsync(ct);
        if (!resolution.Success)
            return resolution.ErrorResult!;

        var agentId = resolution.AgentId;
        var result = await bridge.CreateTaskAsync(
            title: command.Title,
            detail: command.Detail,
            source: ResolveSource(agentId),
            priority: command.Priority ?? "Normal",
            assignedTo: command.AssignedTo ?? agentId,
            projectId: command.ProjectId,
            ct: ct);

        return MapResult(result, "create_task");
    }

    [HttpPost("tasks/{parentTaskId:guid}/children")]
    public async Task<ActionResult<TaskBridgeCommandResponse<DashboardTaskDto>>> CreateChildTask(
        Guid parentTaskId,
        [FromBody] BridgeCreateChildTaskCommand command,
        CancellationToken ct)
    {
        var resolution = await TryResolveAgentAsync(ct);
        if (!resolution.Success)
            return resolution.ErrorResult!;

        var agentId = resolution.AgentId;
        var result = await bridge.CreateChildTaskAsync(
            parentTaskId: parentTaskId,
            title: command.Title,
            detail: command.Detail,
            source: ResolveSource(agentId),
            priority: command.Priority ?? "Normal",
            assignedTo: command.AssignedTo,
            expectedFrom: command.ExpectedFrom ?? command.AssignedTo,
            startsInProgress: command.StartsInProgress,
            ct: ct);

        return MapResult(result, "create_child_task");
    }

    [HttpPatch("tasks/{taskId:guid}/status")]
    public async Task<ActionResult<TaskBridgeCommandResponse<DashboardTaskDto>>> UpdateStatus(
        Guid taskId,
        [FromBody] BridgeUpdateStatusCommand command,
        CancellationToken ct)
    {
        var resolution = await TryResolveAgentAsync(ct);
        if (!resolution.Success)
            return resolution.ErrorResult!;

        var agentId = resolution.AgentId;
        var result = await bridge.UpdateStatusAsync(
            taskId: taskId,
            state: command.State,
            callerAgent: agentId,
            ct: ct);

        return MapResult(result, "update_status");
    }

    [HttpPost("tasks/{taskId:guid}/activity")]
    public async Task<ActionResult<TaskBridgeCommandResponse<ActivityEntryDto>>> AppendActivity(
        Guid taskId,
        [FromBody] BridgeAppendActivityCommand command,
        CancellationToken ct)
    {
        var resolution = await TryResolveAgentAsync(ct);
        if (!resolution.Success)
            return resolution.ErrorResult!;

        var result = await bridge.AppendActivityAsync(
            taskId: taskId,
            message: command.Message,
            type: command.Type ?? "comment",
            ct: ct);

        return MapActivityResult(result, "append_activity");
    }

    [HttpPost("tasks/{taskId:guid}/handoff")]
    public async Task<ActionResult<TaskBridgeCommandResponse<DashboardTaskDto>>> Handoff(
        Guid taskId,
        [FromBody] BridgeHandoffCommand command,
        CancellationToken ct)
    {
        var resolution = await TryResolveAgentAsync(ct);
        if (!resolution.Success)
            return resolution.ErrorResult!;

        var result = await bridge.HandoffAsync(
            taskId: taskId,
            targetAgent: command.TargetAgent,
            note: command.Note,
            ct: ct);

        return MapResult(result, "handoff");
    }

    [HttpGet("board")]
    public async Task<ActionResult<BoardResponse>> GetBoard(CancellationToken ct)
    {
        var resolution = await TryResolveAgentAsync(ct);
        if (!resolution.Success)
            return resolution.ErrorResult!;

        return Ok(await bridge.GetBoardAsync(ct));
    }

    [HttpGet("tasks/{taskId:guid}")]
    public async Task<ActionResult<TaskBridgeCommandResponse<DashboardTaskDto>>> GetTask(
        Guid taskId, CancellationToken ct)
    {
        var resolution = await TryResolveAgentAsync(ct);
        if (!resolution.Success)
            return resolution.ErrorResult!;

        var result = await bridge.GetTaskAsync(taskId, ct);
        return MapResult(result, "get_task");
    }

    [HttpGet("tasks/{taskId:guid}/children")]
    public async Task<ActionResult<List<DashboardTaskDto>>> GetChildren(
        Guid taskId, CancellationToken ct)
    {
        var resolution = await TryResolveAgentAsync(ct);
        if (!resolution.Success)
            return resolution.ErrorResult!;

        return Ok(await bridge.GetChildTasksAsync(taskId, ct));
    }

    [HttpGet("tasks/{taskId:guid}/activity")]
    public async Task<ActionResult<TaskBridgeCommandResponse<List<ActivityEntryDto>>>> GetActivity(
        Guid taskId, CancellationToken ct)
    {
        var resolution = await TryResolveAgentAsync(ct);
        if (!resolution.Success)
            return resolution.ErrorResult!;

        var events = await bridge.GetTaskActivityAsync(taskId, ct);
        var entries = events.Select(e => new ActivityEntryDto(e.Id, e.Type, e.Message, e.CreatedAt)).ToList();

        return Ok(new TaskBridgeCommandResponse<List<ActivityEntryDto>>
        {
            Ok = true,
            Command = "get_activity",
            Data = entries
        });
    }

    [HttpGet("agent-overview")]
    public async Task<ActionResult<AgentWorkflowOverview>> GetAgentOverview(
        CancellationToken ct,
        [FromQuery] int staleHours = 2)
    {
        var resolution = await TryResolveAgentAsync(ct);
        if (!resolution.Success)
            return resolution.ErrorResult!;

        var threshold = TimeSpan.FromHours(Math.Max(1, staleHours));
        return Ok(await bridge.GetAgentOverviewAsync(threshold, ct));
    }

    private async Task<(bool Success, string AgentId, ActionResult? ErrorResult)> TryResolveAgentAsync(CancellationToken ct)
    {
        var allowedAgentIds = await agentService.GetAllowedAgentIdsAsync(ct);
        var allowedActorIds = AgentIdentityCatalog.BuildAllowedActorIds(allowedAgentIds);

        if (!RequestAuthorizationHelper.HasVerifiedAuthentication(HttpContext, configuration))
        {
            var unauthorized = Unauthorized(new { error = ApikeyErrorMessage });
            logger.LogWarning("Bridge: unauthenticated request rejected from {Ip}", HttpContext.Connection.RemoteIpAddress);
            return (false, string.Empty, unauthorized);
        }

        var agentHeader = await RequestAuthorizationHelper.ResolveAgentHeaderAsync(
            HttpContext,
            agentService,
            configuration,
            ct);
        if (agentHeader.AgentId is not null)
            return (true, agentHeader.AgentId, null);

        if (agentHeader.HeaderProvided && !agentHeader.IsRecognized)
            logger.LogWarning(
                "Bridge: ignoring unknown X-Agent-Id from authenticated caller at {Ip} and continuing identity fallback",
                HttpContext.Connection.RemoteIpAddress);

        if (User.Identity?.IsAuthenticated == true)
        {
            var normalizedClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value?.Trim().ToLowerInvariant();
            if (!string.IsNullOrWhiteSpace(normalizedClaim) && allowedActorIds.Contains(normalizedClaim))
                return (true, normalizedClaim, null);

            // Browser JWT fallback is intentionally restricted to board owners/admins.
            // Agent/service traffic should authenticate as an allowed agent or service principal.
            if (User.IsInRole("owner") || User.IsInRole("admin"))
                return (true, "bao", null);
        }

        if (RequestAuthorizationHelper.IsAuthenticatedService(HttpContext, configuration) &&
            allowedActorIds.Contains("nexus-system"))
            return (true, "nexus-system", null);

        var forbidden = StatusCode(StatusCodes.Status403Forbidden, new { error = "Authenticated caller has no permitted bridge identity." });
        logger.LogWarning("Bridge: authenticated caller has no permitted identity from {Ip}", HttpContext.Connection.RemoteIpAddress);
        return (false, string.Empty, forbidden);
    }

    private static string ResolveSource(string agentId) => agentId switch
    {
        "bao" or "nexus-system" => "bao",
        _ => agentId
    };

    private ActionResult MapResult<T>(TaskBridgeResult<T> result, string command) where T : class
    {
        if (result.Outcome == TaskBridgeOutcome.Success)
            return new OkObjectResult(new TaskBridgeCommandResponse<T>
            {
                Ok = true,
                Command = command,
                Data = result.Data,
                Operation = BuildBridgeOperation(command, result.Data)
            });

        var statusCode = result.Outcome switch
        {
            TaskBridgeOutcome.NotFound => 404,
            TaskBridgeOutcome.InvalidState => 422,
            TaskBridgeOutcome.Unauthorized => 403,
            TaskBridgeOutcome.ValidationError => 400,
            _ => 500
        };

        return new ObjectResult(new TaskBridgeCommandResponse<T>
        {
            Ok = false,
            Command = command,
            Error = result.Error ?? "Unknown error"
        }) { StatusCode = statusCode };
    }

    private ActionResult MapActivityResult(TaskBridgeResult<Data.ActivityEvent> result, string command)
    {
        if (result.Outcome == TaskBridgeOutcome.Success)
            return new OkObjectResult(new TaskBridgeCommandResponse<ActivityEntryDto>
            {
                Ok = true,
                Command = command,
                Data = result.Data is null ? null : new ActivityEntryDto(
                    result.Data.Id, result.Data.Type, result.Data.Message, result.Data.CreatedAt),
                Operation = result.Data is null
                    ? null
                    : OperationResultFactory.FromHttpContext(
                        HttpContext,
                        "completed",
                        new EntityRefDto(
                            "activity",
                            result.Data.Id.ToString(),
                            result.Data.Type),
                        affectedRefs: result.Data.TaskId is { } taskId
                            ? [new EntityRefDto("task", taskId.ToString())]
                            : [])
            });

        var statusCode = result.Outcome switch
        {
            TaskBridgeOutcome.NotFound => 404,
            TaskBridgeOutcome.ValidationError => 400,
            _ => 500
        };

        return new ObjectResult(new TaskBridgeCommandResponse<ActivityEntryDto>
        {
            Ok = false,
            Command = command,
            Error = result.Error ?? "Unknown error"
        }) { StatusCode = statusCode };
    }

    private OperationResultDto? BuildBridgeOperation<T>(string command, T? data)
        where T : class
    {
        if (command.StartsWith("get_", StringComparison.Ordinal) || data is null)
            return null;

        if (data is DashboardTaskDto task)
        {
            var affected = new List<EntityRefDto>();
            if (task.ProjectId is { } projectId)
                affected.Add(new EntityRefDto("project", projectId.ToString()));
            if (task.ParentTaskId is { } parentTaskId)
                affected.Add(new EntityRefDto("task", parentTaskId.ToString(), "Parent task"));
            return OperationResultFactory.FromHttpContext(
                HttpContext,
                "completed",
                new EntityRefDto("task", task.Id.ToString(), task.Title),
                affectedRefs: affected);
        }

        return null;
    }
}

public sealed class TaskBridgeCommandResponse<T>
{
    public bool Ok { get; init; }
    public string Command { get; init; } = string.Empty;
    public T? Data { get; init; }
    public string? Error { get; init; }
    public OperationResultDto? Operation { get; init; }
    public string Timestamp { get; init; } = DateTimeOffset.UtcNow.ToString("o");
}

public sealed record BridgeCreateTaskCommand(
    string Title,
    string? Detail = null,
    string? Priority = null,
    string? AssignedTo = null,
    Guid? ProjectId = null
);

public sealed record BridgeCreateChildTaskCommand(
    string Title,
    string? Detail = null,
    string? Priority = null,
    string? AssignedTo = null,
    string? ExpectedFrom = null,
    bool StartsInProgress = false
);

public sealed record BridgeUpdateStatusCommand(string State);

public sealed record BridgeAppendActivityCommand(
    string Message,
    string? Type = null
);

public sealed record BridgeHandoffCommand(
    string TargetAgent,
    string? Note = null
);

public sealed record ActivityEntryDto(
    long Id,
    string Type,
    string Message,
    DateTimeOffset CreatedAt
);
