using System.ComponentModel;
using System.Diagnostics;
using System.Security.Claims;
using ModelContextProtocol.Server;
using Nexus.Api.Controllers;
using Nexus.Api.Data;
using Nexus.Api.Models;

namespace Nexus.Api.Services;

[McpServerToolType]
public sealed class NexusMcpTools(
    ITaskBridgeService bridge,
    IAgentService agentService,
    IHttpContextAccessor httpContextAccessor,
    IConfiguration configuration,
    ILogger<NexusMcpTools> logger,
    IAgentProposalService? agentProposals = null)
{
    // ── P1b: Read-only MCP Tools (TaskBridgeService facade) ──

    [McpServerTool(Name = "nexus_get_board")]
    [Description("Get the full Nexus task board grouped by canonical states.")]
    public async Task<BoardResponse> GetBoard(CancellationToken ct = default)
    {
        await ResolveCallerAsync(ct);
        return await bridge.GetBoardAsync(ct);
    }

    [McpServerTool(Name = "nexus_agent_overview")]
    [Description("Get agent workflow overview, including waiting and stale task groups.")]
    public async Task<AgentWorkflowOverview> GetAgentOverview(
        [Description("Stale threshold in hours. Defaults to 2.")]
        int staleHours = 2,
        CancellationToken ct = default)
    {
        await ResolveCallerAsync(ct);
        return await bridge.GetAgentOverviewAsync(TimeSpan.FromHours(Math.Max(1, staleHours)), ct);
    }

    [McpServerTool(Name = "nexus_get_task")]
    [Description("Get one Nexus task by ID.")]
    public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> GetTask(Guid taskId, CancellationToken ct = default)
    {
        await ResolveCallerAsync(ct);
        return ToResponse(await bridge.GetTaskAsync(taskId, ct), "nexus_get_task");
    }

    [McpServerTool(Name = "nexus_get_children")]
    [Description("Get child tasks for a Nexus parent task.")]
    public async Task<IReadOnlyList<DashboardTaskDto>> GetChildren(Guid parentTaskId, CancellationToken ct = default)
    {
        await ResolveCallerAsync(ct);
        return await bridge.GetChildTasksAsync(parentTaskId, ct);
    }

    [McpServerTool(Name = "nexus_get_activity")]
    [Description("Get activity entries for a Nexus task.")]
    public async Task<IReadOnlyList<ActivityEntryDto>> GetActivity(Guid taskId, CancellationToken ct = default)
    {
        await ResolveCallerAsync(ct);
        var activity = await bridge.GetTaskActivityAsync(taskId, ct);
        return activity.Select(entry => new ActivityEntryDto(entry.Id, entry.Type, entry.Message, entry.CreatedAt)).ToList();
    }

    // ── P1c: Mutating MCP Tools (TaskBridgeService facade) ──
    //
    // Each tool delegates directly to ITaskBridgeService without introducing
    // new business logic. Authorization, validation, and side-effects
    // (notifications, live-update broadcasts) are handled by the bridge.

    /// <summary>
    /// Creates a top-level task on the Nexus board.
    /// The caller (derived from an authenticated identity and optional
    /// X-Agent-Id hint) is set as the default
    /// assignee and source. Priority defaults to "Normal".
    /// </summary>
    [McpServerTool(Name = "nexus_create_task")]
    [Description("Create a top-level Nexus task.")]
    public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> CreateTask(
        [Description("Task title (required).")] string title,
        [Description("Optional long-form description.")] string? detail = null,
        [Description("Priority label. Defaults to 'Normal'.")] string? priority = "Normal",
        [Description("Agent ID to assign the task to. Defaults to the caller.")] string? assignedTo = null,
        CancellationToken ct = default)
    {
        var caller = await ResolveCallerAsync(ct);
        var result = await bridge.CreateTaskAsync(
            title: title,
            detail: detail,
            source: ResolveSource(caller),
            priority: priority,
            assignedTo: assignedTo ?? caller,
            ct: ct);

        return ToResponse(result, "nexus_create_task");
    }

    /// <summary>
    /// Creates a visible child task under a Nexus parent for delegation.
    /// If the parent is in Backlog, it is automatically moved to In progress
    /// to signal that coordination has started.
    /// </summary>
    [McpServerTool(Name = "nexus_create_child_task")]
    [Description("Create a visible child task under a Nexus parent task for delegation.")]
    public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> CreateChildTask(
        [Description("ID of the parent task.")] Guid parentTaskId,
        [Description("Child task title (required).")] string title,
        [Description("Optional long-form description.")] string? detail = null,
        [Description("Priority label. Defaults to 'Normal'.")] string? priority = "Normal",
        [Description("Agent ID to assign. Defaults to the expected-from agent.")] string? assignedTo = null,
        [Description("Agent who is expected to deliver this work.")] string? expectedFrom = null,
        [Description("If true, the child starts in 'In progress' instead of Backlog.")] bool startsInProgress = false,
        CancellationToken ct = default)
    {
        var caller = await ResolveCallerAsync(ct);
        var result = await bridge.CreateChildTaskAsync(
            parentTaskId: parentTaskId,
            title: title,
            detail: detail,
            source: ResolveSource(caller),
            priority: priority,
            assignedTo: assignedTo,
            expectedFrom: expectedFrom ?? assignedTo,
            startsInProgress: startsInProgress,
            ct: ct);

        return ToResponse(result, "nexus_create_child_task");
    }

    /// <summary>
    /// Updates a task's lifecycle state.
    /// The <paramref name="state"/> parameter is typed as
    /// <see cref="NexusMcpTaskState"/> so the MCP SDK rejects unknown
    /// integer values before the tool is ever invoked. Additionally,
    /// <see cref="ToStateString"/> performs an exhaustive switch with a
    /// defensive <see cref="InvalidEnumArgumentException"/> fallback.
    /// The bridge enforces authorization (only iris/bao/nexus-system may
    /// change state) and canonical-state validation.
    /// </summary>
    [McpServerTool(Name = "nexus_update_status")]
    [Description("Update a Nexus task status. The schema only exposes canonical task states.")]
    public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> UpdateStatus(
        [Description("ID of the task to update.")] Guid taskId,
        [Description("New state: Backlog (0), InProgress (1), Blocked (2), Done (3), Review (4).")] NexusMcpTaskState state,
        CancellationToken ct = default)
    {
        var caller = await ResolveCallerAsync(ct);
        var result = await bridge.UpdateStatusAsync(taskId, ToStateString(state), caller, ct);
        return ToResponse(result, "nexus_update_status");
    }

    /// <summary>
    /// Appends an activity entry (comment, status note, or checkpoint) to a task.
    /// This triggers a live-update broadcast so dashboards stay current.
    /// </summary>
    [McpServerTool(Name = "nexus_append_activity")]
    [Description("Append an activity/checkpoint entry to a Nexus task.")]
    public async Task<TaskBridgeCommandResponse<ActivityEntryDto>> AppendActivity(
        [Description("ID of the task to annotate.")] Guid taskId,
        [Description("Activity message text (required).")] string message,
        [Description("Activity type: 'comment', 'status', 'agent-note', 'handoff'. Defaults to 'comment'.")] string? type = "comment",
        CancellationToken ct = default)
    {
        await ResolveCallerAsync(ct);
        var result = await bridge.AppendActivityAsync(taskId, message, type, ct);
        return ToActivityResponse(result, "nexus_append_activity");
    }

    /// <summary>
    /// Marks a task handoff to another agent.
    /// Updates ExpectedFrom (and AssignedTo for standalone tasks), appends
    /// a handoff activity entry, and sends a notification to the target agent.
    /// </summary>
    [McpServerTool(Name = "nexus_handoff")]
    [Description("Mark a task handoff to another known agent and append handoff activity.")]
    public async Task<TaskBridgeCommandResponse<DashboardTaskDto>> Handoff(
        [Description("ID of the task to hand off.")] Guid taskId,
        [Description("Target agent ID (must be a known agent).")] string targetAgent,
        [Description("Optional handoff note.")] string? note = null,
        CancellationToken ct = default)
    {
        await ResolveCallerAsync(ct);
        var result = await bridge.HandoffAsync(taskId, targetAgent, note, ct);
        return ToResponse(result, "nexus_handoff");
    }

    [McpServerTool(
        Name = "nexus_propose_agent",
        ReadOnly = false,
        Destructive = false,
        Idempotent = true,
        OpenWorld = false,
        UseStructuredContent = true,
        OutputSchemaType = typeof(AgentProposalToolResult))]
    [Description(
        "Propose a new OpenClaw agent for explicit Nexus owner approval. "
        + "This tool never calls agents.create and never grants its own proposal approval.")]
    public async Task<AgentProposalToolResult> ProposeAgent(
        [Description("Human-readable agent name. OpenClaw derives a path-safe id.")]
        string name,
        [Description("Stable caller-generated request id used for idempotency.")]
        string clientRequestId,
        [Description("Short operational role for the proposed agent.")]
        string? role = null,
        [Description("Mission and expected outcome for the proposed agent.")]
        string? description = null,
        [Description("Optional configured OpenClaw provider/model id.")]
        string? model = null,
        [Description("Optional identity emoji.")]
        string? emoji = null,
        CancellationToken ct = default)
    {
        var caller = await ResolveCallerAsync(ct);
        if (agentProposals is null)
        {
            return new AgentProposalToolResult(
                false,
                "unavailable",
                "Agent proposal workflow is not registered.",
                null,
                "Use the owner-only Nexus setup center.");
        }

        try
        {
            var result = await agentProposals.CreateAsync(
                new CreateAgentProposalRequest(
                    name,
                    role,
                    description,
                    model,
                    emoji,
                    ClientRequestId: clientRequestId),
                caller == "iris" ? "iris" : "mcp",
                new OpenClawInvocationMetadata(
                    clientRequestId,
                    Activity.Current?.TraceId.ToString()
                        ?? Guid.NewGuid().ToString("N"),
                    caller,
                    Activity.Current?.Id),
                ct);
            return new AgentProposalToolResult(
                result.Ok,
                result.State,
                result.Message,
                result.Proposal,
                result.Recovery);
        }
        catch (AgentProposalValidationException exception)
        {
            return new AgentProposalToolResult(
                false,
                "invalid",
                $"{exception.Field}: {exception.Message}",
                null,
                "Correct the proposal arguments and submit a new clientRequestId.");
        }
    }

    [McpServerTool(
        Name = "nexus_get_agent_proposal",
        ReadOnly = true,
        Destructive = false,
        Idempotent = true,
        OpenWorld = false,
        UseStructuredContent = true,
        OutputSchemaType = typeof(AgentProposalToolResult))]
    [Description(
        "Read one Nexus agent proposal and its approval/provisioning state. "
        + "Proposed markdown content is not returned through MCP.")]
    public async Task<AgentProposalToolResult> GetAgentProposal(
        [Description("Nexus agent proposal id.")]
        Guid proposalId,
        CancellationToken ct = default)
    {
        await ResolveCallerAsync(ct);
        if (agentProposals is null)
        {
            return new AgentProposalToolResult(
                false,
                "unavailable",
                "Agent proposal workflow is not registered.",
                null,
                "Use the owner-only Nexus setup center.");
        }

        var proposal = await agentProposals.GetByIdAsync(
            proposalId,
            includeFileContent: false,
            ct);
        return proposal is null
            ? new AgentProposalToolResult(
                false,
                "not_found",
                "Agent proposal was not found.",
                null,
                null)
            : new AgentProposalToolResult(
                true,
                proposal.Status,
                "Agent proposal loaded.",
                proposal,
                proposal.Error?.Recovery);
    }

    private async Task<string> ResolveCallerAsync(CancellationToken ct)
    {
        var context = httpContextAccessor.HttpContext
            ?? throw new UnauthorizedAccessException("MCP request context is not available.");

        var allowedAgentIds = await agentService.GetAllowedAgentIdsAsync(ct);
        var allowedActorIds = AgentIdentityCatalog.BuildAllowedActorIds(allowedAgentIds);

        if (!RequestAuthorizationHelper.HasVerifiedAuthentication(context, configuration))
        {
            logger.LogWarning("MCP: unauthenticated request rejected from {Ip}", context.Connection.RemoteIpAddress);
            throw new UnauthorizedAccessException("MCP tools require a verified JWT or X-Nexus-Api-Key.");
        }

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

        if (agentHeader.HeaderProvided && !agentHeader.IsRecognized)
            logger.LogWarning(
                "MCP: ignoring unknown X-Agent-Id from authenticated caller at {Ip}",
                context.Connection.RemoteIpAddress);

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

            if (context.User.IsInRole("owner") || context.User.IsInRole("admin"))
                return "bao";
        }

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

        logger.LogWarning("MCP: authenticated request has no permitted identity from {Ip}", context.Connection.RemoteIpAddress);
        throw new UnauthorizedAccessException("MCP caller is authenticated but has no permitted Nexus identity.");
    }

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

    private static string ToStateString(NexusMcpTaskState state) => state switch
    {
        NexusMcpTaskState.Backlog => TaskStateHelper.ToStateString(TaskState.Backlog),
        NexusMcpTaskState.InProgress => TaskStateHelper.ToStateString(TaskState.InProgress),
        NexusMcpTaskState.Blocked => TaskStateHelper.ToStateString(TaskState.Blocked),
        NexusMcpTaskState.Done => TaskStateHelper.ToStateString(TaskState.Done),
        NexusMcpTaskState.Review => TaskStateHelper.ToStateString(TaskState.Review),
        _ => throw new InvalidEnumArgumentException(nameof(state), (int)state, typeof(NexusMcpTaskState))
    };

    private TaskBridgeCommandResponse<T> ToResponse<T>(TaskBridgeResult<T> result, string command) where T : class
        => new()
        {
            Ok = result.Outcome == TaskBridgeOutcome.Success,
            Command = command,
            Data = result.Outcome == TaskBridgeOutcome.Success ? result.Data : null,
            Error = result.Outcome == TaskBridgeOutcome.Success ? null : result.Error ?? result.Outcome.ToString(),
            Operation = result.Outcome == TaskBridgeOutcome.Success
                ? BuildTaskOperation(command, result.Data)
                : null
        };

    private TaskBridgeCommandResponse<ActivityEntryDto> ToActivityResponse(
        TaskBridgeResult<Nexus.Api.Data.ActivityEvent> result,
        string command)
        => new()
        {
            Ok = result.Outcome == TaskBridgeOutcome.Success,
            Command = command,
            Data = result.Data is null
                ? null
                : new ActivityEntryDto(result.Data.Id, result.Data.Type, result.Data.Message, result.Data.CreatedAt),
            Error = result.Outcome == TaskBridgeOutcome.Success ? null : result.Error ?? result.Outcome.ToString(),
            Operation = result.Outcome == TaskBridgeOutcome.Success && result.Data is not null
                ? OperationResultFactory.FromHttpContext(
                    httpContextAccessor.HttpContext
                        ?? throw new UnauthorizedAccessException("MCP request context is unavailable."),
                    "completed",
                    new EntityRefDto("activity", result.Data.Id.ToString(), result.Data.Type),
                    affectedRefs: result.Data.TaskId is { } taskId
                        ? [new EntityRefDto("task", taskId.ToString())]
                        : [])
                : null
        };

    private OperationResultDto? BuildTaskOperation<T>(string command, T? data)
        where T : class
    {
        if (command.StartsWith("nexus_get_", StringComparison.Ordinal) ||
            data is not DashboardTaskDto task)
        {
            return null;
        }

        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(
            httpContextAccessor.HttpContext
                ?? throw new UnauthorizedAccessException("MCP request context is unavailable."),
            "completed",
            new EntityRefDto("task", task.Id.ToString(), task.Title),
            affectedRefs: affected);
    }
}

/// <summary>
/// Canonical task states exposed via the MCP tool schema.
/// Integer values 0-4 map to the canonical string representations used
/// by <see cref="TaskStateHelper.AllStates"/>. The MCP SDK rejects
/// out-of-range integers before the tool is invoked.
/// </summary>
public enum NexusMcpTaskState
{
    /// <summary>Task is in the backlog (not yet started).</summary>
    Backlog = 0,
    /// <summary>Work is actively in progress.</summary>
    InProgress = 1,
    /// <summary>Work is blocked by an external dependency.</summary>
    Blocked = 2,
    /// <summary>Work is complete.</summary>
    Done = 3,
    /// <summary>Work is ready for review.</summary>
    Review = 4
}

/// <summary>
/// Static helpers for <see cref="NexusMcpTaskState"/>.
/// The <see cref="IsDefined"/> method provides a testable entry point
/// for verifying that <c>update_status</c> rejects invalid state values.
/// </summary>
public static class NexusMcpTaskStateHelper
{
    /// <summary>
    /// Returns true when <paramref name="state"/> is one of the five
    /// canonical values defined in <see cref="NexusMcpTaskState"/>.
    /// Rejects undefined cast values (e.g. <c>(NexusMcpTaskState)99</c>).
    /// </summary>
    public static bool IsDefined(NexusMcpTaskState state) =>
        Enum.IsDefined(state);
}
