using System;
using System.IO;
using ClaudeDo.Localization;
using ClaudeDo.Ui.Localization;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels;
using Xunit;

namespace ClaudeDo.Ui.Tests.ViewModels;

public class UsagePillViewModelTests
{
    // Loc.Current is ambient static state defaulting to a key-echo localizer, so these
    // assertions on real strings must install a real one instead of relying on whichever
    // other test class happened to run first.
    public UsagePillViewModelTests()
    {
        var dir = AppContext.BaseDirectory;
        while (dir is not null && !Directory.Exists(Path.Combine(dir, "src", "ClaudeDo.Localization", "locales")))
            dir = Path.GetDirectoryName(dir);
        Loc.Current = new Localizer(
            LocaleStore.Load(Path.Combine(dir!, "src", "ClaudeDo.Localization", "locales")), "en");
    }

    private sealed class FakeWorker : StubWorkerClient
    {
        public UsageSnapshotDto? InitialSnapshot;
        public override Task<UsageSnapshotDto?> GetUsageSnapshotAsync() => Task.FromResult(InitialSnapshot);
    }

    private static UsageSnapshotDto Snapshot(
        double? fiveHourPercent = null,
        DateTimeOffset? fiveHourResetsAt = null,
        double? sevenDayPercent = null,
        DateTimeOffset? sevenDayResetsAt = null,
        int fiveHourThresholdPct = 80,
        int sevenDayThresholdPct = 90,
        bool isGateBlocked = false,
        string? gateReason = null,
        DateTime? fetchedAtUtc = null,
        bool isStale = false,
        string? lastError = null,
        int configuredSlots = 1,
        int effectiveSlots = 1,
        string? throttleBucket = null)
        => new(
            fiveHourPercent, fiveHourResetsAt, sevenDayPercent, sevenDayResetsAt,
            Array.Empty<UsageLimitDto>(), fiveHourThresholdPct, sevenDayThresholdPct,
            isGateBlocked, gateReason, fetchedAtUtc ?? DateTime.UtcNow, isStale, lastError,
            configuredSlots, effectiveSlots, throttleBucket);

    // ── Text formatting ─────────────────────────────────────────────────────

    [Fact]
    public void Text_BothBucketsKnown_ShowsBoth()
    {
        var worker = new FakeWorker { InitialSnapshot = Snapshot(fiveHourPercent: 30, sevenDayPercent: 57) };
        var vm = new UsagePillViewModel(worker);

        Assert.Contains("5h", vm.Text);
        Assert.Contains("30", vm.Text);
        Assert.Contains("7d", vm.Text);
        Assert.Contains("57", vm.Text);
    }

    [Fact]
    public void Text_OnlyFiveHourKnown_ShowsOnlyFiveHour()
    {
        var worker = new FakeWorker { InitialSnapshot = Snapshot(fiveHourPercent: 42) };
        var vm = new UsagePillViewModel(worker);

        Assert.Contains("5h", vm.Text);
        Assert.Contains("42", vm.Text);
        Assert.DoesNotContain("7d", vm.Text);
    }

    [Fact]
    public void Text_NoSnapshot_ShowsEmptyPlaceholder()
    {
        var worker = new FakeWorker();
        var vm = new UsagePillViewModel(worker);

        Assert.Equal("Usage –", vm.Text);
    }

    // ── IsWarn / IsBlocked threshold logic ──────────────────────────────────

    [Fact]
    public void IsWarn_BelowThresholdMinusTen_IsFalse()
    {
        var worker = new FakeWorker { InitialSnapshot = Snapshot(fiveHourPercent: 69, fiveHourThresholdPct: 80) };
        var vm = new UsagePillViewModel(worker);

        Assert.False(vm.IsWarn);
        Assert.False(vm.IsBlocked);
    }

    [Fact]
    public void IsWarn_AtThresholdMinusTen_IsTrue()
    {
        var worker = new FakeWorker { InitialSnapshot = Snapshot(fiveHourPercent: 70, fiveHourThresholdPct: 80) };
        var vm = new UsagePillViewModel(worker);

        Assert.True(vm.IsWarn);
        Assert.False(vm.IsBlocked);
    }

    [Fact]
    public void IsBlocked_WhenGateReportsBlocked_IsTrue()
    {
        var worker = new FakeWorker
        {
            InitialSnapshot = Snapshot(fiveHourPercent: 80, fiveHourThresholdPct: 80, isGateBlocked: true, gateReason: "5h-Limit 80% >= 80%")
        };
        var vm = new UsagePillViewModel(worker);

        Assert.True(vm.IsBlocked);
    }

    // ── Stale ────────────────────────────────────────────────────────────────

    [Fact]
    public void Stale_Snapshot_SetsIsStale_AndTooltipMentionsStamp()
    {
        var fetchedAt = new DateTime(2026, 8, 5, 9, 41, 0, DateTimeKind.Utc);
        var worker = new FakeWorker { InitialSnapshot = Snapshot(isStale: true, fetchedAtUtc: fetchedAt, lastError: "timeout") };
        var vm = new UsagePillViewModel(worker);

        Assert.True(vm.IsStale);
        Assert.Contains(fetchedAt.ToLocalTime().ToString("HH:mm"), vm.Tooltip);
        Assert.Contains("timeout", vm.Tooltip);
    }

    // ── UsageUpdated event ───────────────────────────────────────────────────

    [Fact]
    public void UsageUpdated_Event_UpdatesText()
    {
        var worker = new FakeWorker();
        var vm = new UsagePillViewModel(worker);
        Assert.Equal("Usage –", vm.Text);

        worker.RaiseUsageUpdated(Snapshot(fiveHourPercent: 12));

        Assert.Contains("12", vm.Text);
    }

    // ── ConnectionRestored event ─────────────────────────────────────────────

    [Fact]
    public void ConnectionRestored_Event_LoadsSnapshot()
    {
        var worker = new FakeWorker();
        var vm = new UsagePillViewModel(worker);
        Assert.Null(vm.Snapshot);

        worker.InitialSnapshot = Snapshot(fiveHourPercent: 25);
        worker.RaiseConnectionRestored();

        Assert.NotNull(vm.Snapshot);
        Assert.Contains("25", vm.Text);
    }
}
