using System.ComponentModel;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ModelContextProtocol.Server;

namespace ClaudeDo.Worker.External;

public sealed record RunDto(
    string Id, int RunNumber, string? SessionId, bool IsRetry,
    string? ResultMarkdown, string? StructuredOutputJson, string? ErrorMarkdown,
    int? ExitCode, int? TurnCount, int? TokensIn, int? TokensOut,
    DateTime? StartedAt, DateTime? FinishedAt);

public sealed record TaskLogResult(
    bool Available,
    IReadOnlyList<string> Entries,
    int TotalLines,
    bool Truncated);

[McpServerToolType]
public sealed class RunHistoryMcpTools
{
    private readonly TaskRunRepository _runs;

    public RunHistoryMcpTools(TaskRunRepository runs) => _runs = runs;

    [McpServerTool, Description("List all execution runs for a task (newest run metadata, tokens, turns, result, error).")]
    public async Task<IReadOnlyList<RunDto>> ListRuns(string taskId, CancellationToken cancellationToken)
    {
        var runs = await _runs.GetByTaskIdAsync(taskId, cancellationToken);
        return runs.Select(ToDto).ToList();
    }

    [McpServerTool, Description("Get a single execution run by its run id.")]
    public async Task<RunDto> GetRun(string runId, CancellationToken cancellationToken)
    {
        var run = await _runs.GetByIdAsync(runId, cancellationToken)
            ?? throw new InvalidOperationException($"Run {runId} not found.");
        return ToDto(run);
    }

    [McpServerTool, Description(
        "Fetch log entries from a task's latest run. " +
        "Returns { available, entries, totalLines, truncated }. " +
        "available=false means no log exists yet (task is queued or just started — not an error). " +
        "entries are the individual lines (NDJSON messages) from Claude's streaming output. " +
        "Default: returns the last 50 entries (tail=50). " +
        "tail: override the number of trailing entries to return. " +
        "offset+limit: return entries starting at position offset (0-based); overrides tail when provided. " +
        "truncated=true when fewer entries are returned than totalLines.")]
    public async Task<TaskLogResult> GetTaskLog(
        string taskId,
        int? tail = null,
        int? offset = null,
        int? limit = null,
        CancellationToken cancellationToken = default)
    {
        var run = await _runs.GetLatestByTaskIdAsync(taskId, cancellationToken);
        if (run is null || string.IsNullOrWhiteSpace(run.LogPath) || !File.Exists(run.LogPath))
            return new TaskLogResult(false, [], 0, false);

        string allText;
        try
        {
            await using var fs = new FileStream(
                run.LogPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            using var reader = new StreamReader(fs);
            allText = await reader.ReadToEndAsync(cancellationToken);
        }
        catch (IOException)
        {
            return new TaskLogResult(false, [], 0, false);
        }

        var lines = allText.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
        var totalLines = lines.Length;

        IReadOnlyList<string> entries;
        bool truncated;

        if (offset.HasValue || limit.HasValue)
        {
            var start = Math.Max(0, offset ?? 0);
            var count = limit.HasValue ? Math.Min(limit.Value, totalLines - start) : totalLines - start;
            entries = lines.Skip(start).Take(count).ToArray();
            truncated = start > 0 || (start + count) < totalLines;
        }
        else
        {
            var take = tail ?? 50;
            if (totalLines <= take)
            {
                entries = lines;
                truncated = false;
            }
            else
            {
                entries = lines[^take..];
                truncated = true;
            }
        }

        return new TaskLogResult(true, entries, totalLines, truncated);
    }

    private static RunDto ToDto(TaskRunEntity r) => new(
        r.Id, r.RunNumber, r.SessionId, r.IsRetry,
        r.ResultMarkdown, r.StructuredOutputJson, r.ErrorMarkdown,
        r.ExitCode, r.TurnCount, r.TokensIn, r.TokensOut,
        r.StartedAt, r.FinishedAt);
}
