using System.Collections.ObjectModel;
using System.Text;
using System.Text.Json;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ClaudeDo.Data;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Ui.Helpers;
using ClaudeDo.Ui.Localization;
using ClaudeDo.Ui.Services;
using Microsoft.EntityFrameworkCore;

namespace ClaudeDo.Ui.ViewModels.Islands;

public sealed partial class TaskMonitorViewModel : ViewModelBase, IDisposable
{
    private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
    private readonly IWorkerClient _worker;

    private readonly StreamLineFormatter _formatter = new();
    private readonly StringBuilder _claudeBuf = new();
    private string? _subscribedTaskId;

    public string? SubscribedTaskId => _subscribedTaskId;

    public ObservableCollection<LogLineViewModel> Log { get; } = new();

    [ObservableProperty] private string _agentState = "idle";

    public string AgentStatusLabel => Loc.T($"vm.agentStatus.{AgentState}");
    public bool IsIdle               => AgentState == "idle";
    public bool IsQueued             => AgentState == "queued";
    public bool IsRunning            => AgentState == "running";
    public bool IsWaitingForReview   => AgentState == "review";
    public bool IsWaitingForChildren => AgentState == "children";
    public bool IsDone               => AgentState == "done";
    public bool IsFailed             => AgentState == "failed";
    public bool IsCancelled          => AgentState == "cancelled";

    public bool ShowContinue       => IsFailed || IsCancelled;
    public bool ShowResetAndRetry  => IsFailed || IsCancelled || IsDone;

    public bool ShowRoadblock      => IsFailed;
    public string RoadblockMessage => IsFailed ? "The session ended with an error." : "";

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(ShowSessionOutcome))]
    private string? _sessionOutcome;

    public bool ShowSessionOutcome =>
        !string.IsNullOrWhiteSpace(SessionOutcome)
        && (IsWaitingForReview || IsDone || IsFailed || IsCancelled);

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(ShowRoadblockCard))]
    [NotifyPropertyChangedFor(nameof(HasRoadblock))]
    private string? _roadblocks;

    public bool HasRoadblock => !string.IsNullOrWhiteSpace(Roadblocks);

    public bool ShowRoadblockCard =>
        !string.IsNullOrWhiteSpace(Roadblocks)
        && (IsWaitingForReview || IsDone || IsFailed || IsCancelled);

    private const string RoadblockMarker = "Roadblocks reported during the run:";

    // Captured handler delegates for disposal
    private readonly Action<string, string> _onTaskMessage;
    private readonly Action<string, string, DateTime> _onTaskStarted;
    private readonly Action<string, string, string, DateTime> _onTaskFinished;
    private readonly Action<string> _onTaskUpdated;
    private readonly Action<string, string, string> _onTaskQuestionAsked;
    private readonly Action<string, string> _onTaskQuestionResolved;

    // A question the running task raised via AskUser and is blocking on, plus the answer
    // the user is typing. Ephemeral (in-memory + live events) — the task is still Running.
    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasPendingQuestion))]
    [NotifyCanExecuteChangedFor(nameof(SubmitAnswerCommand))]
    private string? _pendingQuestionId;

    [ObservableProperty] private string? _pendingQuestion;

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(SubmitAnswerCommand))]
    private string _answerDraft = string.Empty;

    public bool HasPendingQuestion => PendingQuestionId is not null;

    public TaskMonitorViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, IWorkerClient worker)
    {
        _dbFactory = dbFactory;
        _worker = worker;

        _onTaskMessage = OnTaskMessage;
        _worker.TaskMessageEvent += _onTaskMessage;

        _onTaskStarted = (slot, taskId, startedAt) =>
        {
            if (taskId == _subscribedTaskId)
                AgentState = "running";
        };
        _worker.TaskStartedEvent += _onTaskStarted;

        _onTaskFinished = (slot, taskId, status, finishedAt) =>
        {
            if (taskId != _subscribedTaskId) return;
            FlushClaudeBuffer();
            Log.Add(new LogLineViewModel
            {
                Kind = LogKind.Done,
                Text = $"── {status.ToUpperInvariant()} · {finishedAt.ToLocalTime():HH:mm:ss} ──",
            });
            AgentState = FinishedStatusToStateKey(status);
            ClearPendingQuestion();
            _ = RefreshOutcomeAsync(taskId);
        };
        _worker.TaskFinishedEvent += _onTaskFinished;

        _onTaskUpdated = taskId =>
        {
            if (taskId == _subscribedTaskId)
                _ = RefreshStatusAsync(taskId);
        };
        _worker.TaskUpdatedEvent += _onTaskUpdated;

        _onTaskQuestionAsked = (taskId, questionId, question) =>
        {
            if (taskId != _subscribedTaskId) return;
            PendingQuestionId = questionId;
            PendingQuestion = question;
        };
        _worker.TaskQuestionAskedEvent += _onTaskQuestionAsked;

        _onTaskQuestionResolved = (taskId, questionId) =>
        {
            if (taskId == _subscribedTaskId && PendingQuestionId == questionId)
                ClearPendingQuestion();
        };
        _worker.TaskQuestionResolvedEvent += _onTaskQuestionResolved;
    }

    private void ClearPendingQuestion()
    {
        PendingQuestionId = null;
        PendingQuestion = null;
        AnswerDraft = string.Empty;
    }

    [RelayCommand(CanExecute = nameof(CanSubmitAnswer))]
    private async System.Threading.Tasks.Task SubmitAnswer()
    {
        var questionId = PendingQuestionId;
        if (questionId is null || string.IsNullOrEmpty(_subscribedTaskId)) return;
        var answer = AnswerDraft;
        if (string.IsNullOrWhiteSpace(answer)) return;
        ClearPendingQuestion();                 // optimistic; the resolved event also clears
        await _worker.AnswerTaskQuestionAsync(_subscribedTaskId, questionId, answer);
    }

    private bool CanSubmitAnswer() => HasPendingQuestion && !string.IsNullOrWhiteSpace(AnswerDraft);

    partial void OnAgentStateChanged(string value)
    {
        OnPropertyChanged(nameof(AgentStatusLabel));
        OnPropertyChanged(nameof(IsIdle));
        OnPropertyChanged(nameof(IsQueued));
        OnPropertyChanged(nameof(IsRunning));
        OnPropertyChanged(nameof(IsWaitingForReview));
        OnPropertyChanged(nameof(IsWaitingForChildren));
        OnPropertyChanged(nameof(IsDone));
        OnPropertyChanged(nameof(IsFailed));
        OnPropertyChanged(nameof(IsCancelled));
        OnPropertyChanged(nameof(ShowContinue));
        OnPropertyChanged(nameof(ShowResetAndRetry));
        OnPropertyChanged(nameof(ShowRoadblock));
        OnPropertyChanged(nameof(RoadblockMessage));
        OnPropertyChanged(nameof(ShowSessionOutcome));
        OnPropertyChanged(nameof(ShowRoadblockCard));
    }

    public void Reset()
    {
        Log.Clear();
        _claudeBuf.Clear();
        _subscribedTaskId = null;
        AgentState = "idle";
        SessionOutcome = null;
        Roadblocks = null;
        ClearPendingQuestion();
    }

    public void SetTaskId(string id) => _subscribedTaskId = id;

    public void ApplyState(ClaudeDo.Data.Models.TaskStatus status) =>
        AgentState = StatusToStateKey(status);

    public void ApplyOutcome(string? result, string? errorFallback)
    {
        if (string.IsNullOrWhiteSpace(result))
        {
            SessionOutcome = errorFallback;
            Roadblocks = null;
            return;
        }

        var idx = result.IndexOf(RoadblockMarker, StringComparison.Ordinal);
        if (idx < 0)
        {
            SessionOutcome = UnwrapSummary(result);
            Roadblocks = null;
            return;
        }

        var summary = result[..idx].TrimEnd().TrimEnd('⚠').TrimEnd();
        SessionOutcome = string.IsNullOrWhiteSpace(summary) ? null : UnwrapSummary(summary);
        Roadblocks = result[(idx + RoadblockMarker.Length)..].Trim();
    }

    /// <summary>Safety net for older rows persisted as raw structured-output JSON: if the outcome
    /// is a JSON object with a string <c>summary</c>, show that; otherwise keep the text as-is.</summary>
    private static string? UnwrapSummary(string? text)
    {
        if (string.IsNullOrWhiteSpace(text)) return text;
        var trimmed = text.TrimStart();
        if (trimmed.Length == 0 || trimmed[0] != '{') return text;
        try
        {
            using var doc = JsonDocument.Parse(trimmed);
            if (doc.RootElement.ValueKind == JsonValueKind.Object &&
                doc.RootElement.TryGetProperty("summary", out var s) &&
                s.ValueKind == JsonValueKind.String)
            {
                var summary = s.GetString();
                if (!string.IsNullOrWhiteSpace(summary)) return summary;
            }
        }
        catch { }
        return text;
    }

    public async System.Threading.Tasks.Task ReplayLogFileAsync(string? logPath, CancellationToken ct)
    {
        if (string.IsNullOrWhiteSpace(logPath)) return;
        var expanded = ExpandUserPath(logPath);
        if (!System.IO.File.Exists(expanded)) return;

        try
        {
            const int maxLines = 2000;
            string[] all;
            await using (var fs = new System.IO.FileStream(
                             expanded,
                             System.IO.FileMode.Open,
                             System.IO.FileAccess.Read,
                             System.IO.FileShare.ReadWrite | System.IO.FileShare.Delete))
            using (var reader = new System.IO.StreamReader(fs))
            {
                var list = new List<string>();
                while (await reader.ReadLineAsync(ct) is { } line)
                    list.Add(line);
                all = list.ToArray();
            }
            ct.ThrowIfCancellationRequested();

            var start = Math.Max(0, all.Length - maxLines);
            for (int i = start; i < all.Length; i++)
            {
                ct.ThrowIfCancellationRequested();
                if (_subscribedTaskId is null) return;
                var line = all[i];
                var normalized = line.StartsWith("[", StringComparison.Ordinal) ? line : "[stdout] " + line;
                OnTaskMessage(_subscribedTaskId, normalized);
            }
            FlushClaudeBuffer();
        }
        catch (OperationCanceledException) { throw; }
        catch { /* best-effort replay */ }
    }

    private void OnTaskMessage(string taskId, string line)
    {
        if (taskId != _subscribedTaskId) return;

        if (line.StartsWith("[stdout]", StringComparison.OrdinalIgnoreCase))
        {
            var body = line["[stdout]".Length..].TrimStart();
            AppendStdoutLine(body);
            return;
        }

        FlushClaudeBuffer();

        var kind = line.StartsWith("[sys]", StringComparison.OrdinalIgnoreCase)    ? LogKind.Sys
                 : line.StartsWith("[tool]", StringComparison.OrdinalIgnoreCase)   ? LogKind.Tool
                 : line.StartsWith("[claude]", StringComparison.OrdinalIgnoreCase) ? LogKind.Claude
                 : line.StartsWith("[stderr]", StringComparison.OrdinalIgnoreCase) ? LogKind.Stderr
                 : line.StartsWith("[done]", StringComparison.OrdinalIgnoreCase)   ? LogKind.Done
                 : LogKind.Msg;
        Log.Add(new LogLineViewModel { Kind = kind, Text = line });
    }

    private void AppendStdoutLine(string line)
    {
        var formatted = _formatter.FormatLine(line);
        if (formatted is null) return;
        _claudeBuf.Append(formatted);
        while (true)
        {
            var text = _claudeBuf.ToString();
            var nl = text.IndexOf('\n');
            if (nl < 0) break;
            var piece = text[..nl].TrimEnd('\r');
            if (!string.IsNullOrWhiteSpace(piece))
                Log.Add(new LogLineViewModel { Kind = LogKind.Claude, Text = piece });
            _claudeBuf.Clear();
            _claudeBuf.Append(text[(nl + 1)..]);
        }
    }

    private void FlushClaudeBuffer()
    {
        if (_claudeBuf.Length == 0) return;
        var piece = _claudeBuf.ToString().TrimEnd();
        _claudeBuf.Clear();
        if (!string.IsNullOrWhiteSpace(piece))
            Log.Add(new LogLineViewModel { Kind = LogKind.Claude, Text = piece });
    }

    private async System.Threading.Tasks.Task RefreshStatusAsync(string taskId)
    {
        try
        {
            await using var ctx = await _dbFactory.CreateDbContextAsync();
            var entity = await ctx.Tasks
                .AsNoTracking()
                .FirstOrDefaultAsync(t => t.Id == taskId);
            if (entity is null || _subscribedTaskId != taskId) return;

            AgentState = StatusToStateKey(entity.Status);
        }
        catch { }
    }

    private async System.Threading.Tasks.Task RefreshOutcomeAsync(string taskId)
    {
        try
        {
            await using var ctx = await _dbFactory.CreateDbContextAsync();
            var entity = await ctx.Tasks.AsNoTracking().FirstOrDefaultAsync(t => t.Id == taskId);
            var latestRun = await new TaskRunRepository(ctx).GetLatestByTaskIdAsync(taskId);
            if (_subscribedTaskId != taskId) return;
            ApplyOutcome(entity?.Result, latestRun?.ErrorMarkdown);
        }
        catch { }
    }

    internal static string StatusToStateKey(ClaudeDo.Data.Models.TaskStatus status) => status switch
    {
        ClaudeDo.Data.Models.TaskStatus.Queued              => "queued",
        ClaudeDo.Data.Models.TaskStatus.Running             => "running",
        ClaudeDo.Data.Models.TaskStatus.WaitingForReview    => "review",
        ClaudeDo.Data.Models.TaskStatus.WaitingForChildren  => "children",
        ClaudeDo.Data.Models.TaskStatus.Done                => "done",
        ClaudeDo.Data.Models.TaskStatus.Failed              => "failed",
        ClaudeDo.Data.Models.TaskStatus.Cancelled           => "cancelled",
        _                                                   => "idle",
    };

    internal static string FinishedStatusToStateKey(string status) => status switch
    {
        "done"                  => "done",
        "failed"                => "failed",
        "cancelled"             => "cancelled",
        "waiting_for_review"    => "review",
        "waiting_for_children"  => "children",
        _                       => status.ToLowerInvariant(),
    };

    private static string ExpandUserPath(string path)
    {
        if (path.StartsWith("~/", StringComparison.Ordinal) || path.StartsWith("~\\", StringComparison.Ordinal))
            return System.IO.Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                path[2..]);
        if (path == "~")
            return Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
        return path;
    }

    public void Dispose()
    {
        _worker.TaskMessageEvent  -= _onTaskMessage;
        _worker.TaskStartedEvent  -= _onTaskStarted;
        _worker.TaskFinishedEvent -= _onTaskFinished;
        _worker.TaskUpdatedEvent  -= _onTaskUpdated;
        _worker.TaskQuestionAskedEvent    -= _onTaskQuestionAsked;
        _worker.TaskQuestionResolvedEvent -= _onTaskQuestionResolved;
    }
}
