using ClaudeDo.Data.Models;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Modals.Settings;

namespace ClaudeDo.Ui.Tests.ViewModels;

public class PrimeClaudeTabViewModelTests
{
    private sealed class FakeApi : IPrimeScheduleApi
    {
        public List<PrimeScheduleDto> Stored { get; } = new();
        public List<PrimeScheduleDto> Upserts { get; } = new();
        public List<Guid> Deletes { get; } = new();
        public Task<List<PrimeScheduleDto>> ListAsync() => Task.FromResult(Stored.ToList());
        public Task<PrimeScheduleDto?> UpsertAsync(PrimeScheduleDto dto)
        {
            Upserts.Add(dto);
            return Task.FromResult<PrimeScheduleDto?>(dto);
        }
        public Task DeleteAsync(Guid id) { Deletes.Add(id); return Task.CompletedTask; }
    }

    private sealed class ThrowingApi : IPrimeScheduleApi
    {
        public string ExceptionMessage { get; init; } = "worker offline";
        public Task<List<PrimeScheduleDto>> ListAsync() => Task.FromResult(new List<PrimeScheduleDto>());
        public Task<PrimeScheduleDto?> UpsertAsync(PrimeScheduleDto dto) => throw new Exception(ExceptionMessage);
        public Task DeleteAsync(Guid id) => Task.CompletedTask;
    }

    private static PrimeScheduleDto Dto(Guid id, int days, TimeSpan time,
        PrimeActionKind kind = PrimeActionKind.FillMyDay, string? prompt = null) =>
        new(id, days, time, true, null, prompt, kind);

    [Fact]
    public async Task Load_Populates_Rows()
    {
        var api = new FakeApi();
        api.Stored.Add(Dto(Guid.NewGuid(), 31, new TimeSpan(7, 0, 0)));
        var vm = new PrimeClaudeTabViewModel(api);
        await vm.LoadAsync();
        Assert.Single(vm.Rows);
    }

    [Fact]
    public void AddSchedule_Appends_Row_With_Defaults()
    {
        var vm = new PrimeClaudeTabViewModel(new FakeApi());
        vm.AddScheduleCommand.Execute(null);
        Assert.Single(vm.Rows);
        Assert.True(vm.Rows[0].Enabled);
        Assert.True(vm.Rows[0].Monday);
        Assert.True(vm.Rows[0].Friday);
        Assert.False(vm.Rows[0].Saturday);
        Assert.Equal(new TimeSpan(7, 0, 0), vm.Rows[0].TimeOfDay);
    }

    [Fact]
    public void Row_Decomposes_And_Recomposes_Days()
    {
        var vm = new PrimeClaudeTabViewModel(new FakeApi());
        vm.AddScheduleCommand.Execute(null);
        var row = vm.Rows[0];
        Assert.Equal(31, row.DaysMask());
        row.Saturday = true;
        Assert.Equal(63, row.DaysMask());
    }

    [Fact]
    public async Task Save_Diffs_New_And_Removed_Rows()
    {
        var api = new FakeApi();
        var keptId = Guid.NewGuid();
        var deletedId = Guid.NewGuid();
        api.Stored.Add(Dto(keptId, 31, new TimeSpan(7, 0, 0)));
        api.Stored.Add(Dto(deletedId, 31, new TimeSpan(8, 0, 0)));

        var vm = new PrimeClaudeTabViewModel(api);
        await vm.LoadAsync();
        vm.RemoveScheduleCommand.Execute(vm.Rows.Single(r => r.Id == deletedId));
        vm.AddScheduleCommand.Execute(null);

        await vm.SaveAsync();

        Assert.Contains(deletedId, api.Deletes);
        Assert.Equal(2, api.Upserts.Count);
    }

    [Fact]
    public void Validate_Reports_No_Days_Selected()
    {
        var vm = new PrimeClaudeTabViewModel(new FakeApi());
        vm.AddScheduleCommand.Execute(null);
        var row = vm.Rows[0];
        row.Monday = row.Tuesday = row.Wednesday = row.Thursday = row.Friday = false;
        Assert.NotNull(vm.Validate());
    }

    [Fact]
    public void Validate_Passes_With_One_Day()
    {
        var vm = new PrimeClaudeTabViewModel(new FakeApi());
        vm.AddScheduleCommand.Execute(null);
        Assert.Null(vm.Validate());
    }

    // SettingsModalViewModel.Save() is the only place that catches Prime.SaveAsync's
    // failures (via a try/catch around the whole settings save) and surfaces them as
    // "Save failed: {message}". For that to work, SaveAsync must propagate the worker's
    // exception rather than swallow it.
    [Fact]
    public async Task Save_WhenApiThrows_PropagatesException()
    {
        var api = new ThrowingApi();
        var vm = new PrimeClaudeTabViewModel(api);
        vm.AddScheduleCommand.Execute(null);

        var ex = await Assert.ThrowsAsync<Exception>(() => vm.SaveAsync());
        Assert.Equal(api.ExceptionMessage, ex.Message);
    }

    [Fact]
    public void AddSchedule_Defaults_To_Ping()
    {
        var vm = new PrimeClaudeTabViewModel(new FakeApi());
        vm.AddScheduleCommand.Execute(null);

        Assert.Equal(PrimeActionKind.Ping, vm.Rows[0].Kind);
        Assert.True(vm.Rows[0].IsPing);
        Assert.False(vm.Rows[0].IsFillMyDay);
        Assert.False(vm.Rows[0].IsCustom);
    }

    [Fact]
    public void Setting_IsCustom_Flips_Kind_And_Clears_The_Others()
    {
        var vm = new PrimeClaudeTabViewModel(new FakeApi());
        vm.AddScheduleCommand.Execute(null);
        var row = vm.Rows[0];

        row.IsCustom = true;

        Assert.Equal(PrimeActionKind.Custom, row.Kind);
        Assert.False(row.IsPing);
        Assert.False(row.IsFillMyDay);
    }

    [Fact]
    public void Row_RadioGroup_Is_Unique_Per_Row()
    {
        var vm = new PrimeClaudeTabViewModel(new FakeApi());
        vm.AddScheduleCommand.Execute(null);
        vm.AddScheduleCommand.Execute(null);

        Assert.NotEqual(vm.Rows[0].RadioGroup, vm.Rows[1].RadioGroup);
    }

    [Fact]
    public void Validate_Rejects_Custom_Without_A_Prompt()
    {
        var vm = new PrimeClaudeTabViewModel(new FakeApi());
        vm.AddScheduleCommand.Execute(null);
        vm.Rows[0].IsCustom = true;
        vm.Rows[0].PromptOverride = "   ";

        Assert.NotNull(vm.Validate());
    }

    [Fact]
    public void Validate_Accepts_Custom_With_A_Prompt()
    {
        var vm = new PrimeClaudeTabViewModel(new FakeApi());
        vm.AddScheduleCommand.Execute(null);
        vm.Rows[0].IsCustom = true;
        vm.Rows[0].PromptOverride = "queue the oldest task";

        Assert.Null(vm.Validate());
    }

    [Fact]
    public void AnyFillMyDay_Tracks_Kind_And_Row_Changes()
    {
        var vm = new PrimeClaudeTabViewModel(new FakeApi());
        Assert.False(vm.AnyFillMyDay);

        vm.AddScheduleCommand.Execute(null);
        Assert.False(vm.AnyFillMyDay);

        vm.Rows[0].IsFillMyDay = true;
        Assert.True(vm.AnyFillMyDay);

        vm.RemoveScheduleCommand.Execute(vm.Rows[0]);
        Assert.False(vm.AnyFillMyDay);
    }

    [Fact]
    public async Task Save_Round_Trips_The_Kind()
    {
        var api = new FakeApi();
        var vm = new PrimeClaudeTabViewModel(api);
        vm.AddScheduleCommand.Execute(null);
        vm.Rows[0].IsCustom = true;
        vm.Rows[0].PromptOverride = "do the thing";

        await vm.SaveAsync();

        Assert.Equal(PrimeActionKind.Custom, api.Upserts[0].Kind);
    }
}
