using System.ComponentModel;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.State;
using ModelContextProtocol.Server;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Worker.Planning;

public sealed record ChildTaskDto(string TaskId, string Title, string? Description, string Status);
public sealed record CreatedChildDto(string TaskId, string Status);

[McpServerToolType]
public sealed class PlanningMcpService
{
    private readonly TaskRepository _tasks;
    private readonly PlanningMcpContextAccessor _contextAccessor;
    private readonly HubBroadcaster _broadcaster;
    private readonly ITaskStateService _state;
    private readonly PlanningChainCoordinator _chain;

    public PlanningMcpService(
        TaskRepository tasks,
        PlanningMcpContextAccessor contextAccessor,
        HubBroadcaster broadcaster,
        ITaskStateService state,
        PlanningChainCoordinator chain)
    {
        _tasks = tasks;
        _contextAccessor = contextAccessor;
        _broadcaster = broadcaster;
        _state = state;
        _chain = chain;
    }

    private Task BroadcastTaskUpdatedAsync(string taskId, CancellationToken ct)
        => _broadcaster.TaskUpdated(taskId);

    [McpServerTool, Description(
        "Create a new draft child task under the current planning session's parent task. " +
        "Set model to the cheapest model that can do this subtask well — 'haiku' for trivial/mechanical " +
        "work, 'sonnet' for normal coding (the default), 'opus' only for complex or cross-cutting work. " +
        "Leave model null to inherit the list/global default. Set maxTurns only when this subtask is " +
        "visibly larger than the default turn budget; leave it null to inherit the list/global default.")]
    public async Task<CreatedChildDto> CreateChildTask(
        string title,
        string? description,
        string? commitType,
        string? model,
        int? maxTurns = null,
        CancellationToken cancellationToken = default)
    {
        var ctx = _contextAccessor.Current;
        var child = await _tasks.CreateChildAsync(ctx.ParentTaskId, title, description, commitType, createdBy: null, model: model, maxTurns: maxTurns, ct: cancellationToken);
        await BroadcastTaskUpdatedAsync(child.Id, cancellationToken);
        await BroadcastTaskUpdatedAsync(ctx.ParentTaskId, cancellationToken);
        return new CreatedChildDto(child.Id, child.Status.ToString());
    }

    [McpServerTool, Description("List all child tasks under the current planning session's parent task.")]
    public async Task<IReadOnlyList<ChildTaskDto>> ListChildTasks(
        CancellationToken cancellationToken)
    {
        var ctx = _contextAccessor.Current;
        var children = await _tasks.GetChildrenAsync(ctx.ParentTaskId, cancellationToken);
        return children
            .Select(c => new ChildTaskDto(c.Id, c.Title, c.Description, c.Status.ToString()))
            .ToList();
    }

    private static readonly TaskStatus[] EditableStatuses =
        { TaskStatus.Idle, TaskStatus.Queued };

    [McpServerTool, Description("Update a child task in the active planning session. Can change title, description, commit type, and status. Status must be one of: Idle, Queued.")]
    public async Task<ChildTaskDto> UpdateChildTask(
        string taskId,
        string? title,
        string? description,
        string? commitType,
        string? status,
        CancellationToken cancellationToken)
    {
        var ctx = _contextAccessor.Current;
        var parent = await _tasks.GetByIdAsync(ctx.ParentTaskId, cancellationToken)
            ?? throw new InvalidOperationException("Planning parent task not found.");
        if (parent.PlanningPhase != PlanningPhase.Active)
            throw new InvalidOperationException("Cannot modify tasks outside an active planning session.");

        var child = await _tasks.GetByIdAsync(taskId, cancellationToken)
            ?? throw new InvalidOperationException($"Task {taskId} not found.");
        if (child.ParentTaskId != ctx.ParentTaskId)
            throw new InvalidOperationException("Task is not a child of this planning session.");

        TaskStatus? newStatus = null;
        if (!string.IsNullOrEmpty(status))
        {
            if (!Enum.TryParse<TaskStatus>(status, ignoreCase: true, out var parsed))
                throw new InvalidOperationException($"Unknown status '{status}'.");
            if (!EditableStatuses.Contains(parsed))
                throw new InvalidOperationException($"Status '{parsed}' cannot be set via MCP. Allowed: Idle, Queued.");
            newStatus = parsed;
        }

        await _tasks.UpdateChildAsync(taskId, title, description, commitType, newStatus, cancellationToken);

        var reload = (await _tasks.GetByIdAsync(taskId, cancellationToken))!;
        await BroadcastTaskUpdatedAsync(reload.Id, cancellationToken);
        await BroadcastTaskUpdatedAsync(ctx.ParentTaskId, cancellationToken);
        return new ChildTaskDto(reload.Id, reload.Title, reload.Description, reload.Status.ToString());
    }

    [McpServerTool, Description("Delete a child task in the active planning session.")]
    public async Task DeleteChildTask(
        string taskId,
        CancellationToken cancellationToken)
    {
        var ctx = _contextAccessor.Current;
        var parent = await _tasks.GetByIdAsync(ctx.ParentTaskId, cancellationToken)
            ?? throw new InvalidOperationException("Planning parent task not found.");
        if (parent.PlanningPhase != PlanningPhase.Active)
            throw new InvalidOperationException("Cannot delete tasks outside an active planning session.");

        var child = await _tasks.GetByIdAsync(taskId, cancellationToken)
            ?? throw new InvalidOperationException($"Task {taskId} not found.");
        if (child.ParentTaskId != ctx.ParentTaskId)
            throw new InvalidOperationException("Task is not a child of this planning session.");

        await _tasks.DeleteAsync(taskId, cancellationToken);
        await BroadcastTaskUpdatedAsync(taskId, cancellationToken);
        await BroadcastTaskUpdatedAsync(ctx.ParentTaskId, cancellationToken);
    }

    [McpServerTool, Description("Update the title and/or description of the parent planning task itself.")]
    public async Task UpdatePlanningTask(
        string? title,
        string? description,
        CancellationToken cancellationToken)
    {
        var ctx = _contextAccessor.Current;
        await _tasks.UpdatePlanningTaskAsync(ctx.ParentTaskId, title, description, cancellationToken);
        await BroadcastTaskUpdatedAsync(ctx.ParentTaskId, cancellationToken);
    }

    [McpServerTool, Description("Finalize the planning session. Child tasks are left idle and chain-linked (each blocked by its predecessor); they are NOT queued automatically — the user queues the plan from the app when ready. The queueAgentTasks argument is accepted for compatibility but ignored.")]
    public async Task<int> Finalize(
        bool queueAgentTasks,
        CancellationToken cancellationToken)
    {
        var ctx = _contextAccessor.Current;

        var finalizeResult = await _state.FinalizePlanningAsync(ctx.ParentTaskId, cancellationToken);
        if (!finalizeResult.Ok)
            throw new InvalidOperationException(
                finalizeResult.Reason ?? $"Could not finalize planning for task {ctx.ParentTaskId}.");

        var children = await _tasks.GetChildrenAsync(ctx.ParentTaskId, cancellationToken);
        int count = children.Count;
        // Establish the blocked-by chain but leave children Idle; queueing is a
        // deliberate user action ("Queue plan"), never an automatic finalize step.
        if (children.Count > 0)
            count = await _chain.SetupChainAsync(ctx.ParentTaskId, enqueue: false, cancellationToken);

        foreach (var c in children)
            await BroadcastTaskUpdatedAsync(c.Id, cancellationToken);
        await BroadcastTaskUpdatedAsync(ctx.ParentTaskId, cancellationToken);
        return count;
    }
}
