using ClaudeDo.Data.Models;
using ClaudeDo.Ui.Localization;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Agent;
using Xunit;
using TaskEntity = ClaudeDo.Data.Models.TaskEntity;

namespace ClaudeDo.Ui.Tests.ViewModels;

public class AgentConfigEditorViewModelTests
{
    private sealed class FakeWorker : StubWorkerClient
    {
        public AppSettingsDto? App;
        public ListConfigDto? ListCfg;
        public List<AgentInfo> AgentList = new();
        public List<SessionSkillDto> InstalledSkills = new();
        public UpdateListConfigDto? SavedListConfig;
        public UpdateTaskAgentSettingsDto? SavedTaskSettings;

        public override Task<AppSettingsDto?> GetAppSettingsAsync() => Task.FromResult(App);
        public override Task<ListConfigDto?> GetListConfigAsync(string listId) => Task.FromResult(ListCfg);
        public override Task<List<AgentInfo>> GetAgentsAsync() => Task.FromResult(AgentList);
        public override Task<List<SessionSkillDto>> GetSessionSkillsAsync() => Task.FromResult(InstalledSkills);
        public override Task UpdateListConfigAsync(UpdateListConfigDto dto) { SavedListConfig = dto; return Task.CompletedTask; }
        public override Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto) { SavedTaskSettings = dto; return Task.CompletedTask; }
    }

    // The global turn default is per-model now, so the preset row for `model` is what carries
    // `turns` — DefaultMaxTurns is no longer consulted.
    private static AppSettingsDto AppWith(string model, int turns) =>
        new(DefaultClaudeInstructions: "", DefaultModel: model, DefaultMaxTurns: turns,
            DefaultPermissionMode: "auto", MaxParallelExecutions: 1, WorktreeStrategy: "sibling",
            CentralWorktreeRoot: null, WorktreeAutoCleanupEnabled: false, WorktreeAutoCleanupDays: 30,
            ReportExcludedPaths: null, StandupWeekday: 3, DailyPrepMaxTasks: 5,
            SessionSkills: null,
            ModelPresets: new List<ModelPresetDto> { new(model, "high", turns) });

    private static TaskEntity TaskWith(string? model, int? turns, string? sp, string? agentPath) =>
        new() { Id = "t1", ListId = "l1", Title = "t", CreatedAt = DateTime.UtcNow,
                Model = model, MaxTurns = turns, SystemPrompt = sp, AgentPath = agentPath };

    private static string Override => Loc.T("settings.inherit.overrideBadge");
    private static string FromList => Loc.T("settings.inherit.inheritedFromList");
    private static string FromGlobal => Loc.T("settings.inherit.inheritedFromGlobal");

    // ── List scope ──────────────────────────────────────────────────────────

    [Fact]
    public async Task List_loads_config_and_global_defaults()
    {
        var w = new FakeWorker { App = AppWith("haiku", 50), ListCfg = new ListConfigDto("opus", "sp", null, 80) };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.List);

        await vm.LoadForListAsync("l1");

        Assert.Equal("opus", vm.Model);
        Assert.Equal(80m, vm.MaxTurns);
        Assert.Equal("sp", vm.SystemPrompt);
        Assert.Equal(Override, vm.ModelBadge);
    }

    [Fact]
    public async Task List_unset_model_inherits_from_global_only()
    {
        var w = new FakeWorker { App = AppWith("haiku", 50), ListCfg = new ListConfigDto(null, null, null, null) };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.List);

        await vm.LoadForListAsync("l1");

        Assert.Null(vm.Model);
        Assert.Equal(FromGlobal, vm.ModelBadge);     // never "from list" at list scope
        Assert.Equal("haiku", vm.ModelInheritedHint);
        Assert.Equal(FromGlobal, vm.TurnsBadge);
        Assert.Equal("50", vm.TurnsInheritedHint);
        Assert.Equal("", vm.EffectiveSystemPromptHint); // list scope shows no inherited prompt
    }

    [Fact]
    public async Task List_reset_clears_to_inherited()
    {
        var w = new FakeWorker { App = AppWith("haiku", 50), ListCfg = new ListConfigDto("opus", "sp", null, 80) };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.List);
        await vm.LoadForListAsync("l1");

        vm.ResetModelCommand.Execute(null);
        vm.ResetTurnsCommand.Execute(null);

        Assert.Null(vm.Model);
        Assert.Null(vm.MaxTurns);
        Assert.Equal(FromGlobal, vm.ModelBadge);
    }

    [Fact]
    public async Task List_save_builds_list_config_dto_and_does_not_auto_save()
    {
        var w = new FakeWorker { App = AppWith("haiku", 50), ListCfg = new ListConfigDto("opus", "sp", null, 80) };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.List);
        await vm.LoadForListAsync("l1");

        vm.Model = "sonnet";
        Assert.Null(w.SavedListConfig); // list scope never auto-saves

        await vm.SaveAsync();

        Assert.NotNull(w.SavedListConfig);
        Assert.Equal("l1", w.SavedListConfig!.ListId);
        Assert.Equal("sonnet", w.SavedListConfig.Model);
        Assert.Equal("sp", w.SavedListConfig.SystemPrompt);
        Assert.Null(w.SavedListConfig.AgentPath);
        Assert.Equal(80, w.SavedListConfig.MaxTurns);
    }

    // ── Task scope ──────────────────────────────────────────────────────────

    [Fact]
    public async Task Task_override_model_shows_override_badge()
    {
        var w = new FakeWorker { App = AppWith("haiku", 50), ListCfg = new ListConfigDto("opus", "list-sp", "/x/list-agent.md", 80) };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.Task);

        await vm.LoadForTaskAsync(TaskWith("sonnet", null, "", null));

        Assert.Equal("sonnet", vm.Model);
        Assert.Equal(Override, vm.ModelBadge);
    }

    [Fact]
    public async Task Task_unset_falls_through_to_list_then_global()
    {
        var w = new FakeWorker { App = AppWith("haiku", 50), ListCfg = new ListConfigDto("opus", "list-sp", null, 80) };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.Task);

        await vm.LoadForTaskAsync(TaskWith(null, null, "", null));

        // model + turns inherit from the LIST tier
        Assert.Equal(FromList, vm.ModelBadge);
        Assert.Equal("opus", vm.ModelInheritedHint);
        Assert.Equal(FromList, vm.TurnsBadge);
        Assert.Equal("80", vm.TurnsInheritedHint);
        Assert.Equal(80, vm.EffectiveMaxTurns);
        Assert.Equal("list-sp", vm.EffectiveSystemPromptHint);
    }

    [Fact]
    public async Task Task_unset_with_no_list_tier_inherits_global()
    {
        var w = new FakeWorker { App = AppWith("haiku", 50), ListCfg = new ListConfigDto(null, null, null, null) };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.Task);

        await vm.LoadForTaskAsync(TaskWith(null, null, "", null));

        Assert.Equal(FromGlobal, vm.ModelBadge);
        Assert.Equal("haiku", vm.ModelInheritedHint);
        Assert.Equal(50, vm.EffectiveMaxTurns);
        Assert.Equal("", vm.EffectiveSystemPromptHint);
    }

    [Fact]
    public async Task Task_save_builds_task_agent_settings_dto()
    {
        var w = new FakeWorker { App = AppWith("haiku", 50), ListCfg = new ListConfigDto(null, null, null, null) };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.Task);
        await vm.LoadForTaskAsync(TaskWith("opus", 120, "task-sp", null));

        await vm.SaveAsync();

        Assert.NotNull(w.SavedTaskSettings);
        Assert.Equal("t1", w.SavedTaskSettings!.TaskId);
        Assert.Equal("opus", w.SavedTaskSettings.Model);
        Assert.Equal("task-sp", w.SavedTaskSettings.SystemPrompt);
        Assert.Null(w.SavedTaskSettings.AgentPath);
        Assert.Equal(120, w.SavedTaskSettings.MaxTurns);
    }

    [Fact]
    public async Task Clear_resets_state_and_target()
    {
        var w = new FakeWorker { App = AppWith("haiku", 50), ListCfg = new ListConfigDto(null, null, null, null) };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.Task);
        await vm.LoadForTaskAsync(TaskWith("opus", 120, "task-sp", null));

        vm.Clear();

        Assert.Null(vm.Model);
        Assert.Null(vm.MaxTurns);
        Assert.Equal("", vm.SystemPrompt);
        Assert.Equal("", vm.EffectiveSystemPromptHint);

        // After Clear, SaveAsync is a no-op (no target).
        w.SavedTaskSettings = null;
        await vm.SaveAsync();
        Assert.Null(w.SavedTaskSettings);
    }

    // ── Session skills ──────────────────────────────────────────────────────

    private static SessionSkillDto Skill(string name) => new(name, "desc-" + name, "url-" + name, "ref", DateTimeOffset.UtcNow);

    [Fact]
    public async Task List_load_reflects_selection_from_list_config()
    {
        var w = new FakeWorker
        {
            App = AppWith("haiku", 50),
            ListCfg = new ListConfigDto(null, null, null, null, new List<string> { "skill-b" }),
            InstalledSkills = new() { Skill("skill-a"), Skill("skill-b") },
        };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.List);

        await vm.LoadForListAsync("l1");

        Assert.Equal(2, vm.SessionSkills.Count);
        Assert.False(vm.SessionSkills.Single(s => s.Name == "skill-a").IsSelected);
        Assert.True(vm.SessionSkills.Single(s => s.Name == "skill-b").IsSelected);
    }

    [Fact]
    public async Task List_save_includes_selected_skill_names()
    {
        var w = new FakeWorker
        {
            App = AppWith("haiku", 50),
            ListCfg = new ListConfigDto(null, null, null, null, null),
            InstalledSkills = new() { Skill("skill-a"), Skill("skill-b") },
        };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.List);
        await vm.LoadForListAsync("l1");

        vm.SessionSkills.Single(s => s.Name == "skill-a").IsSelected = true;
        await vm.SaveAsync();

        Assert.NotNull(w.SavedListConfig);
        Assert.Equal(new List<string> { "skill-a" }, w.SavedListConfig!.SessionSkills);
    }

    [Fact]
    public async Task Task_load_parses_session_skills_json_and_reflects_selection()
    {
        var w = new FakeWorker
        {
            App = AppWith("haiku", 50),
            ListCfg = new ListConfigDto(null, null, null, null),
            InstalledSkills = new() { Skill("skill-a"), Skill("skill-b") },
        };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.Task);
        var entity = TaskWith(null, null, "", null);
        entity.SessionSkills = "[\"skill-a\"]";

        await vm.LoadForTaskAsync(entity);

        Assert.True(vm.SessionSkills.Single(s => s.Name == "skill-a").IsSelected);
        Assert.False(vm.SessionSkills.Single(s => s.Name == "skill-b").IsSelected);
    }

    [Fact]
    public async Task Task_toggling_a_skill_auto_saves_selection()
    {
        var w = new FakeWorker
        {
            App = AppWith("haiku", 50),
            ListCfg = new ListConfigDto(null, null, null, null),
            InstalledSkills = new() { Skill("skill-a") },
        };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.Task);
        await vm.LoadForTaskAsync(TaskWith(null, null, "", null));

        vm.SessionSkills.Single(s => s.Name == "skill-a").IsSelected = true;

        // Poll instead of a fixed sleep: the save is debounced, and a full-suite run loads the
        // thread pool enough that a hard 500 ms wait can expire before the timer callback runs.
        var deadline = DateTime.UtcNow.AddSeconds(5);
        while (DateTime.UtcNow < deadline && w.SavedTaskSettings is null)
            await Task.Delay(25);

        Assert.NotNull(w.SavedTaskSettings);
        Assert.Equal(new List<string> { "skill-a" }, w.SavedTaskSettings!.SessionSkills);
    }

    [Fact]
    public async Task Clear_deselects_all_skills()
    {
        var w = new FakeWorker
        {
            App = AppWith("haiku", 50),
            ListCfg = new ListConfigDto(null, null, null, null),
            InstalledSkills = new() { Skill("skill-a") },
        };
        var vm = new AgentConfigEditorViewModel(w, AgentConfigScope.Task);
        var entity = TaskWith(null, null, "", null);
        entity.SessionSkills = "[\"skill-a\"]";
        await vm.LoadForTaskAsync(entity);

        vm.Clear();

        Assert.All(vm.SessionSkills, s => Assert.False(s.IsSelected));
    }
}
