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

namespace ClaudeDo.Worker.External;

public sealed record TaskListDto(string Id, string Name, string? WorkingDir);

public sealed record TaskDto(
    string Id,
    string ListId,
    string Title,
    string? Description,
    string Status,
    string? Result,
    string? CreatedBy,
    DateTime CreatedAt,
    DateTime? StartedAt,
    DateTime? FinishedAt);

[McpServerToolType]
public sealed class ExternalMcpService
{
    private readonly TaskRepository _tasks;
    private readonly ListRepository _lists;
    private readonly QueueService _queue;
    private readonly HubBroadcaster _broadcaster;
    private readonly ITaskStateService _state;

    public ExternalMcpService(
        TaskRepository tasks,
        ListRepository lists,
        QueueService queue,
        HubBroadcaster broadcaster,
        ITaskStateService state)
    {
        _tasks = tasks;
        _lists = lists;
        _queue = queue;
        _broadcaster = broadcaster;
        _state = state;
    }

    [McpServerTool, Description("List all task lists available in ClaudeDo.")]
    public async Task<IReadOnlyList<TaskListDto>> ListTaskLists(CancellationToken cancellationToken)
    {
        var lists = await _lists.GetAllAsync(cancellationToken);
        return lists.Select(l => new TaskListDto(l.Id, l.Name, l.WorkingDir)).ToList();
    }

    [McpServerTool, Description("List tasks in a given list. Optionally filter by creator (CreatedBy) and/or status.")]
    public async Task<IReadOnlyList<TaskDto>> ListTasks(
        string listId,
        string? createdBy,
        string? status,
        CancellationToken cancellationToken)
    {
        TaskStatus? statusFilter = null;
        if (!string.IsNullOrWhiteSpace(status))
        {
            if (!Enum.TryParse<TaskStatus>(status, ignoreCase: true, out var parsed))
                throw new InvalidOperationException($"Unknown status '{status}'.");
            statusFilter = parsed;
        }

        var tasks = await _tasks.GetByListIdAsync(listId, cancellationToken);
        IEnumerable<TaskEntity> query = tasks;
        if (createdBy is not null)
            query = query.Where(t => t.CreatedBy == createdBy);
        if (statusFilter is not null)
            query = query.Where(t => t.Status == statusFilter);

        return query.Select(ToDto).ToList();
    }

    [McpServerTool, Description("Get a single task by id, including its current status and result.")]
    public async Task<TaskDto> GetTask(string taskId, CancellationToken cancellationToken)
    {
        var task = await _tasks.GetByIdAsync(taskId, cancellationToken)
            ?? throw new InvalidOperationException($"Task {taskId} not found.");
        return ToDto(task);
    }

    [McpServerTool, Description("Create a new task in the given list. Set queueImmediately=true to enqueue it for agent execution.")]
    public async Task<TaskDto> AddTask(
        string listId,
        string title,
        string? description,
        string createdBy,
        bool queueImmediately,
        CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(listId))
            throw new InvalidOperationException("listId is required.");
        if (string.IsNullOrWhiteSpace(title))
            throw new InvalidOperationException("title is required.");
        if (string.IsNullOrWhiteSpace(createdBy))
            throw new InvalidOperationException("createdBy is required.");

        var list = await _lists.GetByIdAsync(listId, cancellationToken)
            ?? throw new InvalidOperationException($"List {listId} not found.");

        var entity = new TaskEntity
        {
            Id = Guid.NewGuid().ToString(),
            ListId = listId,
            Title = title,
            Description = description,
            Status = TaskStatus.Idle,
            CreatedAt = DateTime.UtcNow,
            CommitType = list.DefaultCommitType,
            CreatedBy = createdBy,
        };
        await _tasks.AddAsync(entity, cancellationToken);

        if (queueImmediately)
        {
            // Routes through TaskStateService so the queue is woken automatically.
            var enqueue = await _state.EnqueueAsync(entity.Id, cancellationToken);
            if (!enqueue.Ok)
                throw new InvalidOperationException(enqueue.Reason ?? "Cannot enqueue task.");
            entity.Status = TaskStatus.Queued;
        }

        await _broadcaster.TaskUpdated(entity.Id);
        return ToDto(entity);
    }

    [McpServerTool, Description("Update an existing task's title, description, and/or commit type. Pass null to leave a field unchanged. Refuses if the task is currently Running.")]
    public async Task<TaskDto> UpdateTask(
        string taskId,
        string? title,
        string? description,
        string? commitType,
        CancellationToken cancellationToken)
    {
        var task = await _tasks.GetByIdAsync(taskId, cancellationToken)
            ?? throw new InvalidOperationException($"Task {taskId} not found.");
        if (task.Status == TaskStatus.Running)
            throw new InvalidOperationException("Cannot update a running task. Cancel it first.");

        if (title is not null) task.Title = title;
        if (description is not null) task.Description = description;
        if (commitType is not null) task.CommitType = commitType;
        await _tasks.UpdateAsync(task, cancellationToken);

        var reload = (await _tasks.GetByIdAsync(taskId, cancellationToken))!;
        await _broadcaster.TaskUpdated(taskId);
        return ToDto(reload);
    }

    [McpServerTool, Description("Update a task's status. Only 'Idle' and 'Queued' are permitted — use RunTaskNow or CancelTask for execution control.")]
    public async Task<TaskDto> UpdateTaskStatus(
        string taskId,
        string status,
        CancellationToken cancellationToken)
    {
        if (!Enum.TryParse<TaskStatus>(status, ignoreCase: true, out var target))
            throw new InvalidOperationException($"Unknown status '{status}'.");

        var task = await _tasks.GetByIdAsync(taskId, cancellationToken)
            ?? throw new InvalidOperationException($"Task {taskId} not found.");

        switch (target)
        {
            case TaskStatus.Idle:
                await _tasks.ResetToManualAsync(taskId, cancellationToken);
                await _broadcaster.TaskUpdated(taskId);
                break;

            case TaskStatus.Queued:
                var enqueueResult = await _state.EnqueueAsync(taskId, cancellationToken);
                if (!enqueueResult.Ok)
                    throw new InvalidOperationException(enqueueResult.Reason ?? "Cannot enqueue task.");
                break;

            default:
                throw new InvalidOperationException(
                    $"Status '{target}' is not settable externally. Use RunTaskNow or CancelTask.");
        }

        var reload = (await _tasks.GetByIdAsync(taskId, cancellationToken))!;
        return ToDto(reload);
    }

    [McpServerTool, Description("Immediately run a task in the override execution slot (bypasses the agent queue).")]
    public async Task RunTaskNow(string taskId, CancellationToken cancellationToken)
    {
        try
        {
            await _queue.RunNow(taskId);
        }
        catch (InvalidOperationException)
        {
            throw new InvalidOperationException("Override slot busy. Try again later.");
        }
        catch (KeyNotFoundException)
        {
            throw new InvalidOperationException($"Task {taskId} not found.");
        }
        await _broadcaster.TaskUpdated(taskId);
    }

    [McpServerTool, Description("Cancel a running task. Returns true if the task was running and cancellation was requested.")]
    public async Task<bool> CancelTask(string taskId, CancellationToken cancellationToken)
    {
        var cancelled = _queue.CancelTask(taskId);
        if (cancelled) await _broadcaster.TaskUpdated(taskId);
        return cancelled;
    }

    [McpServerTool, Description("Delete a task. Refuses if the task is currently Running — cancel it first.")]
    public async Task DeleteTask(string taskId, CancellationToken cancellationToken)
    {
        var task = await _tasks.GetByIdAsync(taskId, cancellationToken)
            ?? throw new InvalidOperationException($"Task {taskId} not found.");
        if (task.Status == TaskStatus.Running)
            throw new InvalidOperationException("Cannot delete a running task. Cancel it first.");

        await _tasks.DeleteAsync(taskId, cancellationToken);
        await _broadcaster.TaskUpdated(taskId);
    }

    private static TaskDto ToDto(TaskEntity t) => new(
        t.Id,
        t.ListId,
        t.Title,
        t.Description,
        t.Status.ToString(),
        t.Result,
        t.CreatedBy,
        t.CreatedAt,
        t.StartedAt,
        t.FinishedAt);
}
