using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Skills;
using ClaudeDo.Worker.Tests.Infrastructure;
using Xunit;

namespace ClaudeDo.Worker.Tests.Hub;

public sealed class SessionSkillsHubTests : IDisposable
{
    private readonly DbFixture _db = new();

    public void Dispose() => _db.Dispose();

    private sealed class FakeSessionSkillRegistry : ISessionSkillRegistry
    {
        public List<SessionSkillEntity> Skills { get; } = new();
        public string? InstallUrl { get; private set; }
        public string? UpdateSourceUrl { get; private set; }
        public string? RemoveSourceUrl { get; private set; }
        public Exception? ThrowOnInstall { get; set; }

        public Task<IReadOnlyList<string>> InstallAsync(string url, CancellationToken ct)
        {
            InstallUrl = url;
            if (ThrowOnInstall is not null) throw ThrowOnInstall;
            return Task.FromResult<IReadOnlyList<string>>(new List<string> { "ponytail", "ponytail-help" });
        }

        public Task UpdateAsync(string sourceUrl, CancellationToken ct)
        {
            UpdateSourceUrl = sourceUrl;
            return Task.CompletedTask;
        }

        public Task RemoveAsync(string sourceUrl, CancellationToken ct)
        {
            RemoveSourceUrl = sourceUrl;
            return Task.CompletedTask;
        }

        public Task<IReadOnlyList<SessionSkillEntity>> ListAsync(CancellationToken ct)
            => Task.FromResult<IReadOnlyList<SessionSkillEntity>>(Skills);
    }

    private (WorkerHub hub, FakeSessionSkillRegistry registry) CreateHub()
    {
        var registry = new FakeSessionSkillRegistry();
        var broadcaster = new HubBroadcaster(new CapturingHubContext());
        var hub = new WorkerHub(
            null!, null!, null!, null!, broadcaster, _db.CreateFactory(),
            null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!,
            null!, new ClaudeDo.Worker.Online.OnlineInboxConfig(), new ClaudeDo.Worker.OnlineRefreshTokenStore(new ClaudeDo.Worker.DpapiTokenStore("unused.token")),
            new ClaudeDo.Worker.Runner.PendingQuestionRegistry(), registry);
        hub.Clients = new FakeHubCallerClients(new RecordingClientProxy());
        hub.Context = new FakeHubCallerContext();
        return (hub, registry);
    }

    [Fact]
    public async Task GetSessionSkills_maps_registry_rows_to_dtos()
    {
        var (hub, registry) = CreateHub();
        registry.Skills.Add(new SessionSkillEntity
        {
            Name = "ponytail",
            SourceUrl = "https://example.com/skills.git",
            PinnedRef = "abc123",
            Subpath = "skills/ponytail",
            Description = "A skill",
            AddedAt = DateTimeOffset.UtcNow,
        });

        var result = await hub.GetSessionSkills();

        var dto = Assert.Single(result);
        Assert.Equal("ponytail", dto.Name);
        Assert.Equal("A skill", dto.Description);
        Assert.Equal("https://example.com/skills.git", dto.SourceUrl);
        Assert.Equal("abc123", dto.PinnedRef);
    }

    [Fact]
    public async Task InstallSessionSkill_returns_installed_names_and_forwards_url()
    {
        var (hub, registry) = CreateHub();

        var installed = await hub.InstallSessionSkill("https://example.com/skills.git");

        Assert.Equal(new List<string> { "ponytail", "ponytail-help" }, installed);
        Assert.Equal("https://example.com/skills.git", registry.InstallUrl);
    }

    [Fact]
    public async Task InstallSessionSkill_wraps_InvalidOperationException_as_HubException()
    {
        var (hub, registry) = CreateHub();
        registry.ThrowOnInstall = new InvalidOperationException("boom");

        var ex = await Assert.ThrowsAsync<Microsoft.AspNetCore.SignalR.HubException>(
            () => hub.InstallSessionSkill("https://example.com/skills.git"));
        Assert.Equal("boom", ex.Message);
    }

    [Fact]
    public async Task UpdateSessionSkill_forwards_source_url()
    {
        var (hub, registry) = CreateHub();

        await hub.UpdateSessionSkill("https://example.com/skills.git");

        Assert.Equal("https://example.com/skills.git", registry.UpdateSourceUrl);
    }

    [Fact]
    public async Task RemoveSessionSkill_forwards_source_url()
    {
        var (hub, registry) = CreateHub();

        await hub.RemoveSessionSkill("https://example.com/skills.git");

        Assert.Equal("https://example.com/skills.git", registry.RemoveSourceUrl);
    }

    [Fact]
    public async Task UpdateAppSettings_then_GetAppSettings_RoundTrips_SessionSkills()
    {
        var (hub, _) = CreateHub();
        var current = await hub.GetAppSettings();

        await hub.UpdateAppSettings(current with { SessionSkills = new List<string> { "ponytail", "ponytail-help" } });
        var reloaded = await hub.GetAppSettings();

        Assert.Equal(new List<string> { "ponytail", "ponytail-help" }, reloaded.SessionSkills);
    }

    [Fact]
    public async Task UpdateAppSettings_EmptySessionSkills_PersistsAsNull()
    {
        var (hub, _) = CreateHub();
        var current = await hub.GetAppSettings();

        await hub.UpdateAppSettings(current with { SessionSkills = new List<string> { "ponytail" } });
        await hub.UpdateAppSettings(current with { SessionSkills = new List<string>() });
        var reloaded = await hub.GetAppSettings();

        Assert.Null(reloaded.SessionSkills);
    }

    [Fact]
    public async Task UpdateListConfig_then_GetListConfig_RoundTrips_SessionSkills()
    {
        var (hub, _) = CreateHub();
        var listId = Guid.NewGuid().ToString();
        using (var ctx = _db.CreateContext())
        {
            await new ListRepository(ctx).AddAsync(new ListEntity { Id = listId, Name = "L", CreatedAt = DateTime.UtcNow });
        }

        await hub.UpdateListConfig(new UpdateListConfigDto(listId, null, null, null, null, new List<string> { "ponytail" }));
        var config = await hub.GetListConfig(listId);

        Assert.NotNull(config);
        Assert.Equal(new List<string> { "ponytail" }, config!.SessionSkills);
    }

    [Fact]
    public async Task UpdateTaskAgentSettings_Persists_SessionSkills()
    {
        var (hub, _) = CreateHub();
        var listId = Guid.NewGuid().ToString();
        var taskId = Guid.NewGuid().ToString();
        using (var ctx = _db.CreateContext())
        {
            await new ListRepository(ctx).AddAsync(new ListEntity { Id = listId, Name = "L", CreatedAt = DateTime.UtcNow });
            await new TaskRepository(ctx).AddAsync(new TaskEntity
            {
                Id = taskId, ListId = listId, Title = "T", CreatedAt = DateTime.UtcNow,
            });
        }

        await hub.UpdateTaskAgentSettings(new UpdateTaskAgentSettingsDto(
            taskId, null, null, null, null, new List<string> { "ponytail", "ponytail-help" }));

        using var readCtx = _db.CreateContext();
        var entity = await new TaskRepository(readCtx).GetByIdAsync(taskId);
        Assert.NotNull(entity);
        Assert.Equal("[\"ponytail\",\"ponytail-help\"]", entity!.SessionSkills);
    }
}
