using ClaudeDo.Worker.Usage.TokenTracker;

namespace ClaudeDo.Worker.Tests.Usage.TokenTracker;

public sealed class TokenTrackerServiceTests
{
    private static readonly DateTime Now = new(2026, 8, 24, 9, 0, 0, DateTimeKind.Utc);

    private static (TokenTrackerService Service, FakeTokenTrackerClient Client) Build(DateTime? now = null)
    {
        var client = new FakeTokenTrackerClient();
        var clock = now ?? Now;
        return (new TokenTrackerService(client, new TokenTrackerState(), () => clock), client);
    }

    [Fact]
    public async Task OpenDashboardAsync_ReturnsTheUrlWhenTheCliIsInstalled()
    {
        var (service, client) = Build();

        var dashboard = await service.OpenDashboardAsync();

        Assert.True(dashboard.Ok);
        Assert.Equal("http://127.0.0.1:7680/", dashboard.Url);
        Assert.Equal(1, client.DashboardCalls);
    }

    [Fact]
    public async Task OpenDashboardAsync_MissingCli_ReportsTheProbeErrorWithoutSpawning()
    {
        var (service, client) = Build();
        client.Probe = new TokenTrackerProbe(false, null, true, "v22.1.0", "'tokentracker' not found on PATH.");

        var dashboard = await service.OpenDashboardAsync();

        Assert.False(dashboard.Ok);
        Assert.Null(dashboard.Url);
        Assert.Equal("'tokentracker' not found on PATH.", dashboard.Error);
        Assert.Equal(0, client.DashboardCalls);
    }

    [Fact]
    public async Task RefreshAsync_StoresParsedExport()
    {
        var (service, client) = Build();

        await service.RefreshAsync();

        Assert.Equal(1, client.ExportCalls);
        Assert.Equal(4, service.State.Export!.Sessions.Count);
        Assert.Null(service.State.LastError);
    }

    [Fact]
    public async Task RefreshAsync_RequestsTheConfiguredWindow()
    {
        var (service, client) = Build();

        await service.RefreshAsync();

        Assert.Equal(DateOnly.FromDateTime(Now.ToLocalTime()), client.LastTo);
        Assert.Equal(
            DateOnly.FromDateTime(Now.ToLocalTime()).AddDays(-TokenTrackerService.WindowDays),
            client.LastFrom);
    }

    [Fact]
    public async Task RefreshAsync_Failure_KeepsLastGoodExport()
    {
        var (service, client) = Build();
        await service.RefreshAsync();

        client.ExportResult = new TokenTrackerRunResult(false, "", "tokentracker exited with code 1");
        await service.RefreshAsync();

        Assert.Equal(4, service.State.Export!.Sessions.Count);
        Assert.Equal("tokentracker exited with code 1", service.State.LastError);
    }

    [Fact]
    public async Task RefreshAsync_UnparseableOutput_IsAnError()
    {
        var (service, client) = Build();
        client.ExportResult = new TokenTrackerRunResult(true, "not json", null);

        await service.RefreshAsync();

        Assert.Null(service.State.Export);
        Assert.NotNull(service.State.LastError);
    }

    [Fact]
    public async Task RefreshAsync_UnsupportedFormatVersion_IsAnErrorAndKeepsNoData()
    {
        var (service, client) = Build();
        client.ExportResult = new TokenTrackerRunResult(true, TokenTrackerFixtures.UnsupportedVersionJson, null);

        await service.RefreshAsync();

        Assert.Null(service.State.Export);
        Assert.Contains("99", service.State.LastError);
    }

    [Fact]
    public async Task EnsureFreshAsync_FreshExport_DoesNotFetchAgain()
    {
        var (service, client) = Build();
        await service.RefreshAsync();

        await service.EnsureFreshAsync(TimeSpan.FromMinutes(15));

        Assert.Equal(1, client.ExportCalls);
    }

    [Fact]
    public async Task EnsureFreshAsync_NoExportYet_Fetches()
    {
        var (service, client) = Build();

        await service.EnsureFreshAsync(TimeSpan.FromMinutes(15));

        Assert.Equal(1, client.ExportCalls);
    }

    [Fact]
    public async Task ConcurrentRefresh_RunsTheCliOnce()
    {
        var (service, client) = Build();
        client.BlockExport = true;

        var first = service.RefreshAsync();
        var second = service.RefreshAsync();
        client.Release();
        await Task.WhenAll(first, second);

        Assert.Equal(1, client.ExportCalls);
    }

    [Fact]
    public async Task ProbeAsync_CachesUntilForced()
    {
        var (service, client) = Build();

        await service.ProbeAsync();
        await service.ProbeAsync();
        Assert.Equal(1, client.ProbeCalls);

        await service.ProbeAsync(force: true);
        Assert.Equal(2, client.ProbeCalls);
    }

    [Fact]
    public async Task InstallAsync_ReprobesAfterwards()
    {
        var (service, client) = Build();
        await service.ProbeAsync();

        var result = await service.InstallAsync();

        Assert.True(result.Ok);
        Assert.Equal(1, client.InstallCalls);
        Assert.Equal(2, client.ProbeCalls);
    }
}
