using System.Collections.ObjectModel;
using System.Globalization;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Ui.Services;
using Microsoft.EntityFrameworkCore;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Ui.ViewModels.Islands;

public sealed partial class TasksIslandViewModel : ViewModelBase
{
    private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
    private readonly WorkerClient? _worker;
    private ListNavItemViewModel? _currentList;
    private CancellationTokenSource? _loadCts;

    public event EventHandler? SelectionChanged;
    public event EventHandler? FocusAddTaskRequested;
    public event EventHandler? TasksChanged;
    public void RequestFocusAddTask() => FocusAddTaskRequested?.Invoke(this, EventArgs.Empty);

    public ObservableCollection<TaskRowViewModel> Items { get; } = new();
    public ObservableCollection<TaskRowViewModel> OverdueItems { get; } = new();
    public ObservableCollection<TaskRowViewModel> OpenItems { get; } = new();
    public ObservableCollection<TaskRowViewModel> CompletedItems { get; } = new();

    [ObservableProperty] private string _newTaskTitle = "";
    [ObservableProperty] private TaskRowViewModel? _selectedTask;
    [ObservableProperty] private string _headerTitle = "";
    [ObservableProperty] private string _headerEyebrow = "";
    [ObservableProperty] private string _subtitle = "";
    [ObservableProperty] private string _statusPill = "";
    [ObservableProperty] private bool _hasStatusPill;
    [ObservableProperty] private bool _isShowingCompleted = true;
    [ObservableProperty] private bool _hasOverdue;
    [ObservableProperty] private bool _hasOpen;
    [ObservableProperty] private bool _hasCompleted;
    [ObservableProperty] private bool _showOpenLabel;
    [ObservableProperty] private string _completedHeader = "COMPLETED";

    public TasksIslandViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, WorkerClient? worker = null)
    {
        _dbFactory = dbFactory;
        _worker = worker;
    }

    public void LoadForList(ListNavItemViewModel? list)
    {
        _loadCts?.Cancel();
        _loadCts?.Dispose();
        _loadCts = new CancellationTokenSource();
        var ct = _loadCts.Token;

        _currentList = list;
        Items.Clear();
        OverdueItems.Clear();
        OpenItems.Clear();
        CompletedItems.Clear();
        HasOverdue    = false;
        HasOpen       = false;
        HasCompleted  = false;
        ShowOpenLabel = false;
        if (list is null) return;

        HeaderTitle = list.Name;
        HeaderEyebrow = DateTime.Now.ToString("dddd · MMM dd", CultureInfo.InvariantCulture).ToUpperInvariant();

        _ = LoadForListAsync(list, ct);
    }

    private async Task LoadForListAsync(ListNavItemViewModel list, CancellationToken ct)
    {
        try
        {
            await using var db = await _dbFactory.CreateDbContextAsync(ct);
            var all = await db.Tasks
                .Include(t => t.List)
                .Include(t => t.Worktree)
                .OrderBy(t => t.SortOrder).ThenBy(t => t.CreatedAt)
                .ToListAsync(ct);

            ct.ThrowIfCancellationRequested();

            IEnumerable<TaskEntity> filtered = list.Kind switch
            {
                ListKind.Smart when list.Id == "smart:my-day"      => all.Where(t => t.IsMyDay),
                ListKind.Smart when list.Id == "smart:important"   => all.Where(t => t.IsStarred),
                ListKind.Smart when list.Id == "smart:planned"     => all.Where(t => t.ScheduledFor != null),
                ListKind.Virtual when list.Id == "virtual:queued"  => all.Where(t => t.Status == TaskStatus.Queued),
                ListKind.Virtual when list.Id == "virtual:running" => all.Where(t => t.Status == TaskStatus.Running),
                ListKind.Virtual when list.Id == "virtual:review"  => all.Where(t => t.Status == TaskStatus.Done && t.Worktree?.State == WorktreeState.Active),
                ListKind.User  => all.Where(t => $"user:{t.ListId}" == list.Id),
                _              => Enumerable.Empty<TaskEntity>(),
            };

            foreach (var t in filtered)
                Items.Add(TaskRowViewModel.FromEntity(t));

            Regroup();
            UpdateSubtitle();
        }
        catch (OperationCanceledException) { }
    }

    private void Regroup()
    {
        OverdueItems.Clear();
        OpenItems.Clear();
        CompletedItems.Clear();

        var today = DateTime.Today;
        foreach (var r in Items)
        {
            if (r.Done)
                CompletedItems.Add(r);
            else if (r.ScheduledFor is { } d && d.Date < today)
                OverdueItems.Add(r);
            else
                OpenItems.Add(r);
        }

        HasOverdue     = OverdueItems.Count > 0;
        HasOpen        = OpenItems.Count > 0;
        HasCompleted   = CompletedItems.Count > 0;
        ShowOpenLabel  = HasOpen && HasOverdue;
        CompletedHeader = $"COMPLETED · {CompletedItems.Count}";
    }

    private void UpdateSubtitle()
    {
        var now     = DateTime.Now;
        var open    = Items.Count(i => !i.Done);
        var running = Items.Count(i => i.Status == TaskStatus.Running);
        var review  = Items.Count(i => i.Status == TaskStatus.Done && i.Branch != null);

        Subtitle = open == 1 ? "1 open task" : $"{open} open tasks";

        if (running > 0 || review > 0)
        {
            StatusPill    = $"{running} running · {review} review";
            HasStatusPill = true;
        }
        else
        {
            StatusPill    = "";
            HasStatusPill = false;
        }
    }

    [RelayCommand]
    private async Task AddAsync()
    {
        if (string.IsNullOrWhiteSpace(NewTaskTitle) || _currentList?.Kind != ListKind.User) return;
        var listId = _currentList.Id["user:".Length..];
        await using var db = await _dbFactory.CreateDbContextAsync();
        var maxSort = await db.Tasks
            .Where(t => t.ListId == listId)
            .Select(t => (int?)t.SortOrder)
            .MaxAsync();
        var entity = new TaskEntity
        {
            Id        = Guid.NewGuid().ToString("N"),
            ListId    = listId,
            Title     = NewTaskTitle.Trim(),
            CreatedAt = DateTime.UtcNow,
            SortOrder = (maxSort ?? -1) + 1,
        };
        db.Tasks.Add(entity);
        await db.SaveChangesAsync();
        var row = TaskRowViewModel.FromEntity(entity);
        Items.Add(row);
        Regroup();
        NewTaskTitle = "";
        UpdateSubtitle();
        TasksChanged?.Invoke(this, EventArgs.Empty);
    }

    public bool CanReorder => _currentList?.Kind == ListKind.User;

    public void ClearDropHints()
    {
        foreach (var r in Items)
        {
            r.DropHintAbove = false;
            r.DropHintBelow = false;
        }
    }

    public void SetDropHint(TaskRowViewModel target, bool placeBelow)
    {
        foreach (var r in Items)
        {
            var isTarget = ReferenceEquals(r, target);
            r.DropHintAbove = isTarget && !placeBelow;
            r.DropHintBelow = isTarget && placeBelow;
        }
    }

    public async Task ReorderAsync(TaskRowViewModel source, TaskRowViewModel target, bool placeBelow)
    {
        if (!CanReorder || _currentList is null) return;
        if (source.IsRunning || target.IsRunning) return;
        if (ReferenceEquals(source, target)) return;

        // Master Items: single Move event (no Reset) so ItemsControls animate, not rebuild.
        MoveWithinCollection(Items, source, target, placeBelow);

        // Apply the same move in whichever section the row lives in.
        // Reorder never changes which section (Open/Overdue/Completed) a row belongs to —
        // that's determined by Done flag and ScheduledFor date, not drag-drop.
        var sourceSection = SectionFor(source);
        var targetSection = SectionFor(target);
        if (sourceSection is not null && ReferenceEquals(sourceSection, targetSection))
            MoveWithinCollection(sourceSection, source, target, placeBelow);

        var listId = _currentList.Id["user:".Length..];
        var orderedIds = Items.Select(i => i.Id).ToList();

        await using var db = await _dbFactory.CreateDbContextAsync();
        var idSet = orderedIds.ToHashSet();
        var entities = await db.Tasks
            .Where(t => t.ListId == listId && idSet.Contains(t.Id))
            .ToListAsync();
        for (int i = 0; i < orderedIds.Count; i++)
        {
            var e = entities.FirstOrDefault(x => x.Id == orderedIds[i]);
            if (e is not null) e.SortOrder = i;
        }
        await db.SaveChangesAsync();
    }

    private static void MoveWithinCollection(
        System.Collections.ObjectModel.ObservableCollection<TaskRowViewModel> coll,
        TaskRowViewModel source,
        TaskRowViewModel target,
        bool placeBelow)
    {
        var srcIdx = coll.IndexOf(source);
        var tgtIdx = coll.IndexOf(target);
        if (srcIdx < 0 || tgtIdx < 0 || srcIdx == tgtIdx) return;

        var finalIdx = placeBelow ? tgtIdx + 1 : tgtIdx;
        if (srcIdx < finalIdx) finalIdx--;
        if (finalIdx < 0) finalIdx = 0;
        if (finalIdx >= coll.Count) finalIdx = coll.Count - 1;
        if (finalIdx == srcIdx) return;

        coll.Move(srcIdx, finalIdx);
    }

    private System.Collections.ObjectModel.ObservableCollection<TaskRowViewModel>? SectionFor(TaskRowViewModel row)
    {
        if (OverdueItems.Contains(row))   return OverdueItems;
        if (OpenItems.Contains(row))      return OpenItems;
        if (CompletedItems.Contains(row)) return CompletedItems;
        return null;
    }

    [RelayCommand]
    private async Task ToggleDoneAsync(TaskRowViewModel row)
    {
        row.Done = !row.Done;
        await using var db = await _dbFactory.CreateDbContextAsync();
        var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
        if (entity != null)
        {
            entity.Status = row.Done ? TaskStatus.Done : TaskStatus.Manual;
            row.Status = entity.Status;
            await db.SaveChangesAsync();
        }
        Regroup();
        UpdateSubtitle();
        TasksChanged?.Invoke(this, EventArgs.Empty);
    }

    [RelayCommand]
    private async Task ToggleStarAsync(TaskRowViewModel row)
    {
        row.IsStarred = !row.IsStarred;
        await using var db = await _dbFactory.CreateDbContextAsync();
        var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
        if (entity != null)
        {
            entity.IsStarred = row.IsStarred;
            await db.SaveChangesAsync();
        }
        TasksChanged?.Invoke(this, EventArgs.Empty);
    }

    [RelayCommand]
    private async Task SendToQueueAsync(TaskRowViewModel? row)
    {
        if (row is null || row.IsRunning) return;
        await using var db = await _dbFactory.CreateDbContextAsync();
        var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
        if (entity is null) return;
        entity.Status = TaskStatus.Queued;
        await db.SaveChangesAsync();
        row.Status = TaskStatus.Queued;
        if (_worker is not null)
        {
            try { await _worker.WakeQueueAsync(); } catch { }
        }
        Regroup();
        UpdateSubtitle();
        TasksChanged?.Invoke(this, EventArgs.Empty);
    }

    [RelayCommand]
    private async Task RemoveFromQueueAsync(TaskRowViewModel? row)
    {
        if (row is null) return;
        await using var db = await _dbFactory.CreateDbContextAsync();
        var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
        if (entity is null) return;
        entity.Status = TaskStatus.Manual;
        await db.SaveChangesAsync();
        row.Status = TaskStatus.Manual;
        Regroup();
        UpdateSubtitle();
        TasksChanged?.Invoke(this, EventArgs.Empty);
    }

    public async Task SetScheduledForAsync(TaskRowViewModel row, DateTime? when)
    {
        if (row is null) return;
        await using var db = await _dbFactory.CreateDbContextAsync();
        var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
        if (entity is null) return;
        entity.ScheduledFor = when;
        await db.SaveChangesAsync();
        row.ScheduledFor = when;
        Regroup();
        UpdateSubtitle();
        TasksChanged?.Invoke(this, EventArgs.Empty);
    }

    [RelayCommand]
    private Task ClearScheduleAsync(TaskRowViewModel? row) =>
        row is null ? Task.CompletedTask : SetScheduledForAsync(row, null);

    [RelayCommand]
    private void Select(TaskRowViewModel row) => SelectedTask = row;

    [RelayCommand]
    private void ToggleShowCompleted() => IsShowingCompleted = !IsShowingCompleted;

    [RelayCommand]
    private void Sort() { /* placeholder — UI-only */ }

    [RelayCommand]
    private void More() { /* placeholder — UI-only */ }

    partial void OnSelectedTaskChanged(TaskRowViewModel? value)
    {
        foreach (var i in Items) i.IsSelected = ReferenceEquals(i, value);
        SelectionChanged?.Invoke(this, EventArgs.Empty);
    }
}
