using ClaudeDo.Worker;
using ClaudeDo.Worker.Config;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Online;
using ClaudeDo.Worker.Tests.Infrastructure;
using System.Text.Json;
using System.Text.Json.Nodes;
using Xunit;

namespace ClaudeDo.Worker.Tests.Hub;

public sealed class OnlineInboxHubTests : IDisposable
{
    private readonly string _configPath = Path.Combine(
        Path.GetTempPath(), $"online_inbox_hub_{Guid.NewGuid():N}.json");
    private readonly string _tokenPath = Path.Combine(
        Path.GetTempPath(), $"online_inbox_hub_token_{Guid.NewGuid():N}.bin");

    public void Dispose()
    {
        try { File.Delete(_configPath); } catch { }
        try { File.Delete(_tokenPath); } catch { }
    }

    private (WorkerHub hub, OnlineInboxConfig inboxCfg, DpapiTokenStore store) CreateHub(
        OnlineInboxConfig? inboxCfg = null, DpapiTokenStore? store = null)
    {
        var cfg = new WorkerConfig();
        inboxCfg ??= cfg.OnlineInbox;
        store ??= new DpapiTokenStore(_tokenPath);
        var broadcaster = new HubBroadcaster(new CapturingHubContext());
        var hub = new WorkerHub(
            null!, null!, null!, null!, broadcaster, null!,
            null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!,
            cfg, inboxCfg, new OnlineRefreshTokenStore(store), new ClaudeDo.Worker.Runner.PendingQuestionRegistry(), null!);
        hub.Clients = new FakeHubCallerClients(new RecordingClientProxy());
        hub.Context = new FakeHubCallerContext();
        return (hub, inboxCfg, store);
    }

    [Fact]
    public void GetOnlineInboxState_ReflectsConfigAndNoToken()
    {
        var (hub, _, _) = CreateHub();

        var state = hub.GetOnlineInboxState();

        Assert.False(state.Enabled);
        Assert.False(state.SignedIn);
        Assert.Equal("", state.ApiBaseUrl);
    }

    [Fact]
    public void GetOnlineInboxState_SignedIn_WhenTokenPresent()
    {
        if (!OperatingSystem.IsWindows()) return; // DPAPI is Windows-only

        var (hub, _, store) = CreateHub();
        store.Save("my-refresh-token");

        var state = hub.GetOnlineInboxState();

        Assert.True(state.SignedIn);
    }

    [Fact]
    public void GetOnlineInboxState_ReflectsConfigFields()
    {
        var inboxCfg = new OnlineInboxConfig
        {
            Enabled = true,
            ApiBaseUrl = "https://inbox.example.com",
            RedirectUri = "http://localhost:9999/callback",
            Zitadel = new ZitadelClientConfig
            {
                Authority = "https://auth.example.com",
                ClientId = "client-abc",
                Scopes = "openid offline_access",
            },
        };
        var (hub, _, _) = CreateHub(inboxCfg);

        var state = hub.GetOnlineInboxState();

        Assert.True(state.Enabled);
        Assert.Equal("https://inbox.example.com", state.ApiBaseUrl);
        Assert.Equal("https://auth.example.com", state.Authority);
        Assert.Equal("client-abc", state.ClientId);
        Assert.Equal("openid offline_access", state.Scopes);
        Assert.Equal("http://localhost:9999/callback", state.RedirectUri);
    }

    [Fact]
    public void SetOnlineInboxConfig_UpdatesConfigAndPersistsToFile()
    {
        var cfg = new WorkerConfig();
        // point SaveOnlineInbox to our temp file
        var inboxCfg = cfg.OnlineInbox;
        var store = new DpapiTokenStore(_tokenPath);
        var broadcaster = new HubBroadcaster(new CapturingHubContext());

        // Patch cfg to save to our temp path by writing an initial file there
        File.WriteAllText(_configPath, "{}");

        // We need to use the internal SaveOnlineInbox with our path.
        // WorkerHub calls _cfg.SaveOnlineInbox() which uses DefaultConfigPath.
        // For this test, directly exercise the round-trip via WorkerConfig.SaveOnlineInbox.
        cfg.OnlineInbox.Enabled = true;
        cfg.OnlineInbox.ApiBaseUrl = "https://inbox.test.com";
        cfg.OnlineInbox.PollIntervalSeconds = 120;
        cfg.OnlineInbox.RedirectUri = "http://localhost:7777/cb";
        cfg.OnlineInbox.Zitadel.Authority = "https://auth.test.com";
        cfg.OnlineInbox.Zitadel.ClientId = "test-client";
        cfg.OnlineInbox.Zitadel.Scopes = "openid offline_access";

        cfg.SaveOnlineInbox(_configPath);

        // Verify file contains only online_inbox node and preserves others
        var json = File.ReadAllText(_configPath);
        var root = JsonNode.Parse(json)!.AsObject();

        Assert.NotNull(root["online_inbox"]);
        var inbox = root["online_inbox"]!.AsObject();
        Assert.True(inbox["enabled"]!.GetValue<bool>());
        Assert.Equal("https://inbox.test.com", inbox["api_base_url"]!.GetValue<string>());
        Assert.Equal(120, inbox["poll_interval_seconds"]!.GetValue<int>());
        Assert.Equal("http://localhost:7777/cb", inbox["redirect_uri"]!.GetValue<string>());

        // Verify existing file keys are preserved (not overwritten)
        var roundTripped = WorkerConfig.Load(_configPath);
        Assert.Equal("https://inbox.test.com", roundTripped.OnlineInbox.ApiBaseUrl);
        Assert.Equal(true, roundTripped.OnlineInbox.Enabled);
    }

    [Fact]
    public void SetOnlineInboxConfig_PreservesOtherConfigKeys()
    {
        // Write a config with a non-inbox key
        var initial = """{"signalr_port":12345}""";
        File.WriteAllText(_configPath, initial);

        var cfg = new WorkerConfig();
        cfg.OnlineInbox.ApiBaseUrl = "https://new.example.com";
        cfg.SaveOnlineInbox(_configPath);

        var root = JsonNode.Parse(File.ReadAllText(_configPath))!.AsObject();
        Assert.Equal(12345, root["signalr_port"]!.GetValue<int>());
        Assert.Equal("https://new.example.com",
            root["online_inbox"]!["api_base_url"]!.GetValue<string>());
    }

    [Fact]
    public void SetOnlineInboxAuth_WritesToken()
    {
        if (!OperatingSystem.IsWindows()) return;

        var (hub, _, store) = CreateHub();
        hub.SetOnlineInboxAuth("refresh-abc");

        Assert.Equal("refresh-abc", store.Read());
    }

    [Fact]
    public void ClearOnlineInboxAuth_RemovesToken()
    {
        if (!OperatingSystem.IsWindows()) return;

        var (hub, _, store) = CreateHub();
        store.Save("some-token");

        hub.ClearOnlineInboxAuth();

        Assert.Null(store.Read());
    }
}
