using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Localization;
using ClaudeDo.Ui;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Modals;
using ClaudeDo.Ui.ViewModels.Modals.Settings;
using Microsoft.EntityFrameworkCore;
using Xunit;

namespace ClaudeDo.Ui.Tests.ViewModels;

public class SettingsModalViewModelTests : IDisposable
{
    private readonly string _dbPath =
        Path.Combine(Path.GetTempPath(), $"claudedo_settingsmodal_test_{Guid.NewGuid():N}.db");

    public SettingsModalViewModelTests()
    {
        using var ctx = NewContext();
        ctx.Database.EnsureCreated();
    }

    public void Dispose()
    {
        try { File.Delete(_dbPath); } catch { }
        try { File.Delete(_dbPath + "-wal"); } catch { }
        try { File.Delete(_dbPath + "-shm"); } catch { }
    }

    private ClaudeDoDbContext NewContext()
    {
        var opts = new DbContextOptionsBuilder<ClaudeDoDbContext>()
            .UseSqlite($"Data Source={_dbPath}")
            .Options;
        return new ClaudeDoDbContext(opts);
    }


    private sealed class FakeWorker : StubWorkerClient
    {
        public AppSettingsDto? AppToReturn;
        public AppSettingsDto? Saved;
        public List<SessionSkillDto> InstalledSkills = new();
        public override Task<AppSettingsDto?> GetAppSettingsAsync() => Task.FromResult(AppToReturn);
        public override Task UpdateAppSettingsAsync(AppSettingsDto dto) { Saved = dto; return Task.CompletedTask; }
        public override Task<List<SessionSkillDto>> GetSessionSkillsAsync() => Task.FromResult(InstalledSkills);
    }

    private sealed class FakePrimeApi : IPrimeScheduleApi
    {
        public Task<List<PrimeScheduleDto>> ListAsync() => Task.FromResult(new List<PrimeScheduleDto>());
        public Task<PrimeScheduleDto?> UpsertAsync(PrimeScheduleDto dto) => Task.FromResult<PrimeScheduleDto?>(dto);
        public Task DeleteAsync(Guid id) => Task.CompletedTask;
    }

    private static Localizer MakeLocalizer()
    {
        var dir = Path.Combine(Path.GetTempPath(), "loc_" + Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(dir);
        File.WriteAllText(Path.Combine(dir, "en.json"),
            """{ "metadata": { "code": "en", "name": "English" }, "settings": { "save": "Save" } }""");
        File.WriteAllText(Path.Combine(dir, "de.json"),
            """{ "metadata": { "code": "de", "name": "Deutsch" }, "settings": { "save": "Speichern" } }""");
        return new Localizer(LocaleStore.Load(dir), "en");
    }

    private static AppSettingsDto DtoWith(int fiveHourPct, int sevenDayPct) =>
        new(DefaultClaudeInstructions: "", DefaultModel: "sonnet", DefaultMaxTurns: 30,
            DefaultPermissionMode: "auto", MaxParallelExecutions: 1, WorktreeStrategy: "sibling",
            CentralWorktreeRoot: null, WorktreeAutoCleanupEnabled: false, WorktreeAutoCleanupDays: 7,
            ReportExcludedPaths: null, StandupWeekday: 3, DailyPrepMaxTasks: 5,
            SessionSkills: null, ModelPresets: null,
            UsageGateFiveHourPct: fiveHourPct, UsageGateSevenDayPct: sevenDayPct);

    [Fact]
    public async Task Save_carries_dragged_throttle_stages_through_untouched()
    {
        // The throttle stages are only editable by dragging the usage-monitor gauges. Saving the
        // Settings modal rebuilds the whole DTO, so it must not reset them to the defaults.
        var worker = new FakeWorker
        {
            AppToReturn = DtoWith(65, 95) with
            {
                UsageThrottleFiveHourSoftPct = 42,
                UsageThrottleFiveHourHardPct = 58,
                UsageThrottleSevenDaySoftPct = 71,
                UsageThrottleSevenDayHardPct = 88,
            },
        };
        var vm = MakeVm(worker);
        await vm.LoadAsync();

        await vm.SaveCommand.ExecuteAsync(null);

        Assert.NotNull(worker.Saved);
        Assert.Equal(42, worker.Saved!.UsageThrottleFiveHourSoftPct);
        Assert.Equal(58, worker.Saved.UsageThrottleFiveHourHardPct);
        Assert.Equal(71, worker.Saved.UsageThrottleSevenDaySoftPct);
        Assert.Equal(88, worker.Saved.UsageThrottleSevenDayHardPct);
    }

    private SettingsModalViewModel MakeVm(FakeWorker worker) =>
        new(worker, new PrimeClaudeTabViewModel(new FakePrimeApi()), new OnlineLoginService(),
            MakeLocalizer(), new AppSettings(), new TestDbFactory(NewContext));

    [Fact]
    public async Task LoadAsync_fills_usage_gate_fields_from_dto()
    {
        var worker = new FakeWorker { AppToReturn = DtoWith(65, 95) };
        var vm = MakeVm(worker);

        await vm.LoadAsync();

        Assert.Equal(65, vm.General.UsageGateFiveHourPct);
        Assert.Equal(95, vm.General.UsageGateSevenDayPct);
    }

    [Fact]
    public async Task Save_sends_usage_gate_fields_in_dto()
    {
        var worker = new FakeWorker();
        var vm = MakeVm(worker);
        vm.General.UsageGateFiveHourPct = 42;
        vm.General.UsageGateSevenDayPct = 77;

        await vm.SaveCommand.ExecuteAsync(null);

        Assert.NotNull(worker.Saved);
        Assert.Equal(42, worker.Saved!.UsageGateFiveHourPct);
        Assert.Equal(77, worker.Saved.UsageGateSevenDayPct);
    }

    [Fact]
    public async Task HasLinkedRepo_false_when_no_list_has_a_working_dir()
    {
        using (var ctx = NewContext())
        {
            ctx.Lists.Add(new ListEntity { Id = "l1", Name = "No repo", CreatedAt = DateTime.UtcNow, WorkingDir = null });
            ctx.SaveChanges();
        }
        var vm = MakeVm(new FakeWorker());

        await vm.LoadAsync();

        Assert.False(vm.HasLinkedRepo);
    }

    [Fact]
    public async Task HasLinkedRepo_true_when_a_list_has_a_working_dir()
    {
        using (var ctx = NewContext())
        {
            ctx.Lists.Add(new ListEntity { Id = "l1", Name = "Repo", CreatedAt = DateTime.UtcNow, WorkingDir = @"C:\repo" });
            ctx.SaveChanges();
        }
        var vm = MakeVm(new FakeWorker());

        await vm.LoadAsync();

        Assert.True(vm.HasLinkedRepo);
    }

    [Fact]
    public async Task HasLinkedRepo_false_when_a_list_working_dir_is_whitespace_only()
    {
        using (var ctx = NewContext())
        {
            ctx.Lists.Add(new ListEntity { Id = "l1", Name = "Blank repo", CreatedAt = DateTime.UtcNow, WorkingDir = "   " });
            ctx.SaveChanges();
        }
        var vm = MakeVm(new FakeWorker());

        await vm.LoadAsync();

        Assert.False(vm.HasLinkedRepo);
    }

    [Fact]
    public void SelectedCategory_updates_SelectedIndex_and_ignores_null()
    {
        var vm = MakeVm(new FakeWorker());
        var worktrees = vm.Categories.Single(c => c.Key == "Worktrees");

        vm.SelectedCategory = worktrees;
        Assert.Equal(vm.Categories.ToList().IndexOf(worktrees), vm.SelectedIndex);

        // Simulated deselection push from the sibling sidebar ListBox — must not clobber the pick.
        vm.SelectedCategory = null;
        Assert.Equal(worktrees, vm.SelectedCategory);
    }

    [Fact]
    public void Categories_default_to_first_entry_and_mark_the_repo_gated_ones()
    {
        var vm = MakeVm(new FakeWorker());

        Assert.Equal(vm.Categories[0], vm.SelectedCategory);
        Assert.True(vm.Categories.Single(c => c.Key == "Worktrees").RequiresRepo);
        Assert.True(vm.Categories.Single(c => c.Key == "Prime").RequiresRepo);
        Assert.True(vm.Categories.Single(c => c.Key == "SessionSkills").RequiresRepo);
        Assert.True(vm.Categories.Single(c => c.Key == "Reports").RequiresRepo);
        Assert.False(vm.Categories.Single(c => c.Key == "General").RequiresRepo);
        Assert.False(vm.Categories.Single(c => c.Key == "Files").RequiresRepo);
    }

    [Fact]
    public async Task SessionSkillSelection_survives_a_save_and_reload_round_trip()
    {
        // The only real regression risk of the Settings-sidebar rework: Session Skills used to be
        // two separate lists (a General-tab checkbox list + the Skills-tab management list) and is
        // now one merged list. Setting the selection, saving, and reloading into a *fresh* VM must
        // come back with the identical selection.
        var installed = new List<SessionSkillDto>
        {
            new("alpha", "", "url-alpha", "ref", DateTimeOffset.UtcNow),
            new("beta", "", "url-beta", "ref", DateTimeOffset.UtcNow),
        };
        var worker = new FakeWorker { AppToReturn = DtoWith(80, 90), InstalledSkills = installed };
        var vm = MakeVm(worker);
        await vm.LoadAsync();

        vm.SessionSkills.Skills.Single(s => s.Name == "alpha").IsSelected = true;
        vm.SessionSkills.Skills.Single(s => s.Name == "beta").IsSelected = false;

        await vm.SaveCommand.ExecuteAsync(null);

        Assert.Equal(new List<string> { "alpha" }, worker.Saved!.SessionSkills);

        // Reload into a brand-new VM instance, as if the modal were closed and reopened.
        var worker2 = new FakeWorker { AppToReturn = worker.Saved, InstalledSkills = installed };
        var vm2 = MakeVm(worker2);
        await vm2.LoadAsync();

        Assert.True(vm2.SessionSkills.Skills.Single(s => s.Name == "alpha").IsSelected);
        Assert.False(vm2.SessionSkills.Skills.Single(s => s.Name == "beta").IsSelected);
        Assert.Equal(new List<string> { "alpha" }, vm2.SessionSkills.SelectedSkillNames());
    }
}
