using System.Collections.ObjectModel;
using Avalonia.Threading;
using ClaudeDo.Data.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;

namespace ClaudeDo.Ui.Services;

public record ActiveTask(string Slot, string TaskId, DateTime StartedAt);

sealed class IndefiniteRetryPolicy : IRetryPolicy
{
    private static readonly TimeSpan[] _delays =
    [
        TimeSpan.Zero,
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(5),
        TimeSpan.FromSeconds(10),
        TimeSpan.FromSeconds(30),
    ];

    public TimeSpan? NextRetryDelay(RetryContext retryContext) =>
        _delays[Math.Min(retryContext.PreviousRetryCount, _delays.Length - 1)];
}

public partial class WorkerClient : ObservableObject, IAsyncDisposable
{
    private readonly HubConnection _hub;
    private CancellationTokenSource? _startCts;
    private Task _retryLoopTask = Task.CompletedTask;
    private readonly object _startLock = new();

    [ObservableProperty]
    private bool _isConnected;

    [ObservableProperty]
    private bool _isReconnecting;

    public ObservableCollection<ActiveTask> ActiveTasks { get; } = new();

    public event Action<string, string, DateTime>? TaskStartedEvent;
    public event Action<string, string, string, DateTime>? TaskFinishedEvent;
    public event Action<string, string>? TaskMessageEvent;
    public event Action<string>? TaskUpdatedEvent;
    public event Action<string>? WorktreeUpdatedEvent;
    public event Action<string>? RunNowRequestedEvent;
    public event Action<string>? ListUpdatedEvent;

    public WorkerClient(string signalRUrl)
    {
        _hub = new HubConnectionBuilder()
            .WithUrl(signalRUrl)
            .WithAutomaticReconnect(new IndefiniteRetryPolicy())
            .Build();

        _hub.Reconnected += async _ =>
        {
            Dispatcher.UIThread.Post(() => { IsConnected = true; IsReconnecting = false; });
            await SeedActiveTasksAsync();
        };

        _hub.Reconnecting += _ =>
        {
            Dispatcher.UIThread.Post(() => { IsConnected = false; IsReconnecting = true; });
            return Task.CompletedTask;
        };

        _hub.Closed += _ =>
        {
            Dispatcher.UIThread.Post(() =>
            {
                IsConnected = false;
                IsReconnecting = false;
                ActiveTasks.Clear();
            });
            return Task.CompletedTask;
        };

        _hub.On<string, string, DateTime>("TaskStarted", (slot, taskId, startedAt) =>
        {
            Dispatcher.UIThread.Post(() =>
            {
                ActiveTasks.Add(new ActiveTask(slot, taskId, startedAt));
                TaskStartedEvent?.Invoke(slot, taskId, startedAt);
            });
        });

        _hub.On<string, string, string, DateTime>("TaskFinished", (slot, taskId, status, finishedAt) =>
        {
            Dispatcher.UIThread.Post(() =>
            {
                var existing = ActiveTasks.FirstOrDefault(t => t.TaskId == taskId);
                if (existing is not null)
                    ActiveTasks.Remove(existing);
                TaskFinishedEvent?.Invoke(slot, taskId, status, finishedAt);
            });
        });

        _hub.On<string, string>("TaskMessage", (taskId, line) =>
        {
            Dispatcher.UIThread.Post(() => TaskMessageEvent?.Invoke(taskId, line));
        });

        _hub.On<string>("TaskUpdated", taskId =>
        {
            Dispatcher.UIThread.Post(() => TaskUpdatedEvent?.Invoke(taskId));
        });

        _hub.On<string>("WorktreeUpdated", taskId =>
        {
            Dispatcher.UIThread.Post(() => WorktreeUpdatedEvent?.Invoke(taskId));
        });

        _hub.On<string>("ListUpdated", listId =>
        {
            Dispatcher.UIThread.Post(() => ListUpdatedEvent?.Invoke(listId));
        });
    }

    public Task StartAsync()
    {
        lock (_startLock)
        {
            if (!_retryLoopTask.IsCompleted)
                return Task.CompletedTask;

            var old = _startCts;
            _startCts = new CancellationTokenSource();
            old?.Cancel();
            old?.Dispose();

            _retryLoopTask = ConnectWithRetryAsync(_startCts.Token);
        }
        return Task.CompletedTask;
    }

    private async Task ConnectWithRetryAsync(CancellationToken ct)
    {
        var delays = new[] { 0, 2, 5, 10, 30 };
        int attempt = 0;
        Dispatcher.UIThread.Post(() => IsReconnecting = true);
        while (!ct.IsCancellationRequested)
        {
            try
            {
                await _hub.StartAsync(ct);
                Dispatcher.UIThread.Post(() => { IsConnected = true; IsReconnecting = false; });
                await SeedActiveTasksAsync();
                return;
            }
            catch (OperationCanceledException)
            {
                return;
            }
            catch
            {
                var delay = delays[Math.Min(attempt++, delays.Length - 1)];
                try { await Task.Delay(TimeSpan.FromSeconds(delay), ct); }
                catch (OperationCanceledException) { return; }
            }
        }
    }

    public async Task StopAsync()
    {
        _startCts?.Cancel();
        try { await _retryLoopTask; } catch (OperationCanceledException) { } catch { /* swallow */ }
        try { await _hub.StopAsync(); } catch { /* swallow */ }
    }

    public async Task RunNowAsync(string taskId)
    {
        RunNowRequestedEvent?.Invoke(taskId);
        await _hub.InvokeAsync("RunNow", taskId);
    }

    public async Task ContinueTaskAsync(string taskId, string followUpPrompt)
    {
        await _hub.InvokeAsync("ContinueTask", taskId, followUpPrompt);
    }

    public async Task ResetTaskAsync(string taskId)
    {
        await _hub.InvokeAsync("ResetTask", taskId);
    }

    public async Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage)
    {
        return await _hub.InvokeAsync<MergeResultDto>(
            "MergeTask", taskId, targetBranch, removeWorktree, commitMessage);
    }

    public async Task<MergeTargetsDto?> GetMergeTargetsAsync(string taskId)
    {
        try
        {
            return await _hub.InvokeAsync<MergeTargetsDto>("GetMergeTargets", taskId);
        }
        catch
        {
            return null;
        }
    }

    public async Task CancelTaskAsync(string taskId)
    {
        await _hub.InvokeAsync("CancelTask", taskId);
    }

    public async Task WakeQueueAsync()
    {
        await _hub.InvokeAsync("WakeQueue");
    }

    public async Task<List<AgentInfo>> GetAgentsAsync()
    {
        try
        {
            var agents = await _hub.InvokeAsync<List<AgentInfo>>("GetAgents");
            return agents ?? [];
        }
        catch
        {
            return [];
        }
    }

    public async Task RefreshAgentsAsync()
    {
        await _hub.InvokeAsync("RefreshAgents");
    }

    public async Task<SeedResultDto?> RestoreDefaultAgentsAsync()
    {
        try
        {
            return await _hub.InvokeAsync<SeedResultDto>("RestoreDefaultAgents");
        }
        catch
        {
            return null;
        }
    }

    private async Task SeedActiveTasksAsync()
    {
        try
        {
            var active = await _hub.InvokeAsync<List<ActiveTaskDto>>("GetActive");
            Dispatcher.UIThread.Post(() =>
            {
                ActiveTasks.Clear();
                foreach (var a in active)
                    ActiveTasks.Add(new ActiveTask(a.Slot, a.TaskId, a.StartedAt));
            });
        }
        catch (HubException)
        {
            // Expected: worker doesn't support GetActive yet
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine($"SeedActiveTasksAsync failed: {ex}");
        }
    }

    public async ValueTask DisposeAsync()
    {
        _startCts?.Cancel();
        try { await _retryLoopTask; } catch (OperationCanceledException) { } catch { /* swallow */ }
        await _hub.DisposeAsync();
    }

    public async Task<AppSettingsDto?> GetAppSettingsAsync()
    {
        try
        {
            return await _hub.InvokeAsync<AppSettingsDto>("GetAppSettings");
        }
        catch
        {
            return null;
        }
    }

    public async Task UpdateAppSettingsAsync(AppSettingsDto dto)
    {
        await _hub.InvokeAsync("UpdateAppSettings", dto);
    }

    public async Task UpdateListAsync(UpdateListDto dto)
    {
        await _hub.InvokeAsync("UpdateList", dto);
    }

    public async Task UpdateListConfigAsync(UpdateListConfigDto dto)
    {
        await _hub.InvokeAsync("UpdateListConfig", dto);
    }

    public async Task<ListConfigDto?> GetListConfigAsync(string listId)
    {
        return await _hub.InvokeAsync<ListConfigDto?>("GetListConfig", listId);
    }

    public async Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto)
    {
        await _hub.InvokeAsync("UpdateTaskAgentSettings", dto);
    }

    public async Task<WorktreeCleanupDto?> CleanupFinishedWorktreesAsync()
    {
        try
        {
            return await _hub.InvokeAsync<WorktreeCleanupDto>("CleanupFinishedWorktrees");
        }
        catch
        {
            return null;
        }
    }

    public async Task<WorktreeResetDto?> ResetAllWorktreesAsync()
    {
        try
        {
            return await _hub.InvokeAsync<WorktreeResetDto>("ResetAllWorktrees");
        }
        catch
        {
            return null;
        }
    }

    // DTOs for deserializing hub responses
    private sealed class ActiveTaskDto
    {
        public string Slot { get; set; } = "";
        public string TaskId { get; set; } = "";
        public DateTime StartedAt { get; set; }
    }
}

public sealed record AppSettingsDto(
    string DefaultClaudeInstructions,
    string DefaultModel,
    int DefaultMaxTurns,
    string DefaultPermissionMode,
    string WorktreeStrategy,
    string? CentralWorktreeRoot,
    bool WorktreeAutoCleanupEnabled,
    int WorktreeAutoCleanupDays);

public sealed record WorktreeCleanupDto(int Removed);
public sealed record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks);
public record MergeResultDto(string Status, IReadOnlyList<string> ConflictFiles, string? ErrorMessage);
public record MergeTargetsDto(string DefaultBranch, IReadOnlyList<string> LocalBranches);
public sealed record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType);
public sealed record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath);
public sealed record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath);
public sealed record ListConfigDto(string? Model, string? SystemPrompt, string? AgentPath);
public sealed record SeedResultDto(int Copied, int Skipped);
