import CodexBarCore
import Foundation
import Testing
@testable import CodexBar

struct UsageStoreResetBoundaryRefreshTests {
    @Test
    func `schedules refresh at reset boundary before normal poll`() {
        let now = Date(timeIntervalSince1970: 1000)
        let resetsAt = now.addingTimeInterval(10 * 60)
        let snapshot = Self.snapshot(updatedAt: now, primaryResetsAt: resetsAt)

        let refreshAt = UsageStore.nextResetBoundaryRefreshDate(
            snapshots: [.codex: snapshot],
            normalRefreshInterval: 30 * 60,
            now: now)

        #expect(refreshAt == resetsAt.addingTimeInterval(UsageStore.resetBoundaryRefreshGraceSeconds))
    }

    @Test
    func `low power mode does not schedule reset boundary before thirty minutes`() {
        let now = Date(timeIntervalSince1970: 1500)
        let resetsAt = now.addingTimeInterval(5 * 60)
        let snapshot = Self.snapshot(updatedAt: now, primaryResetsAt: resetsAt)

        let refreshAt = UsageStore.nextResetBoundaryRefreshDate(
            snapshots: [.codex: snapshot],
            normalRefreshInterval: 60 * 60,
            minimumAutomaticRefreshInterval: BackgroundWorkPowerPolicy.lowPowerMinimumInterval,
            now: now)

        #expect(refreshAt == now.addingTimeInterval(30 * 60))
    }

    @Test
    func `schedules prompt refresh when reset boundary already passed`() {
        let now = Date(timeIntervalSince1970: 2000)
        let resetsAt = now.addingTimeInterval(-3 * 60)
        let snapshot = Self.snapshot(
            updatedAt: resetsAt.addingTimeInterval(-60),
            primaryResetsAt: resetsAt)

        let refreshAt = UsageStore.nextResetBoundaryRefreshDate(
            snapshots: [.codex: snapshot],
            normalRefreshInterval: 30 * 60,
            now: now)

        #expect(refreshAt == now.addingTimeInterval(UsageStore.resetBoundaryRefreshMinimumDelaySeconds))
    }

    @Test
    func `suppresses repeated prompt refresh after attempted boundary`() {
        let now = Date(timeIntervalSince1970: 2500)
        let resetsAt = now.addingTimeInterval(-3 * 60)
        let boundaryRefreshAt = resetsAt.addingTimeInterval(UsageStore.resetBoundaryRefreshGraceSeconds)
        let snapshot = Self.snapshot(
            updatedAt: resetsAt.addingTimeInterval(-60),
            primaryResetsAt: resetsAt)

        let refreshAt = UsageStore.nextResetBoundaryRefreshDate(
            snapshots: [.codex: snapshot],
            normalRefreshInterval: 30 * 60,
            attemptedBoundaryRefreshes: [boundaryRefreshAt],
            now: now)

        #expect(refreshAt == nil)
    }

    @Test
    func `in flight boundary refresh remains retryable`() {
        let now = Date(timeIntervalSince1970: 2750)
        let resetsAt = now.addingTimeInterval(-3 * 60)
        let boundaryRefreshAt = resetsAt.addingTimeInterval(UsageStore.resetBoundaryRefreshGraceSeconds)
        let snapshot = Self.snapshot(
            updatedAt: resetsAt.addingTimeInterval(-60),
            primaryResetsAt: resetsAt)

        #expect(UsageStore.shouldRecordResetBoundaryAttempt(isRefreshing: true) == false)
        #expect(UsageStore.shouldRecordResetBoundaryAttempt(isRefreshing: false) == true)

        let refreshAt = UsageStore.nextResetBoundaryRefreshDate(
            snapshots: [.codex: snapshot],
            normalRefreshInterval: 30 * 60,
            attemptedBoundaryRefreshes: [],
            now: now)

        #expect(refreshAt == now.addingTimeInterval(UsageStore.resetBoundaryRefreshMinimumDelaySeconds))

        let suppressedAfterRecordedAttempt = UsageStore.nextResetBoundaryRefreshDate(
            snapshots: [.codex: snapshot],
            normalRefreshInterval: 30 * 60,
            attemptedBoundaryRefreshes: [boundaryRefreshAt],
            now: now)

        #expect(suppressedAfterRecordedAttempt == nil)
    }

    @Test
    @MainActor
    func `in flight boundary refresh clears fired schedule marker`() async {
        let now = Date(timeIntervalSince1970: 2800)
        let resetsAt = now.addingTimeInterval(-3 * 60)
        let boundaryRefreshAt = resetsAt.addingTimeInterval(UsageStore.resetBoundaryRefreshGraceSeconds)
        let retryAt = now.addingTimeInterval(UsageStore.resetBoundaryRefreshMinimumDelaySeconds)
        let snapshot = Self.snapshot(
            updatedAt: resetsAt.addingTimeInterval(-60),
            primaryResetsAt: resetsAt)
        let settings = testSettingsStore(suiteName: "UsageStoreResetBoundaryRefreshTests-inflight-marker")
        let store = UsageStore(
            fetcher: UsageFetcher(environment: [:]),
            browserDetection: BrowserDetection(cacheTTL: 0),
            settings: settings,
            startupBehavior: .testing)
        store.snapshots[.codex] = snapshot
        store.isRefreshing = true
        store.scheduledResetBoundaryRefreshAt = retryAt

        await store.runResetBoundaryRefresh(boundaryRefreshAt: boundaryRefreshAt)

        #expect(store.scheduledResetBoundaryRefreshAt == nil)
        #expect(store.attemptedResetBoundaryRefreshes.isEmpty)

        store.isRefreshing = false
        store.scheduleResetBoundaryRefreshIfNeeded(normalRefreshInterval: 30 * 60, now: now)
        defer { store.cancelResetBoundaryRefresh() }

        #expect(store.scheduledResetBoundaryRefreshAt == retryAt)
    }

    @Test
    @MainActor
    func `boundary refresh records its attempt before waiting for forced enrichment`() async {
        let settings = testSettingsStore(suiteName: "UsageStoreResetBoundaryRefreshTests-waits-for-tail")
        settings.statusChecksEnabled = false
        settings.costUsageEnabled = true
        settings.openAIWebAccessEnabled = false
        settings.codexCookieSource = .off
        for provider in UsageProvider.allCases {
            guard let metadata = ProviderRegistry.shared.metadata[provider] else { continue }
            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: provider == .codex)
        }
        let store = UsageStore(
            fetcher: UsageFetcher(environment: [:]),
            browserDetection: BrowserDetection(cacheTTL: 0),
            settings: settings,
            startupBehavior: .testing)
        let tokenGate = BlockingForcedTokenRefresh()
        var providerRefreshes = 0
        var didObserveWait = false
        store._test_providerRefreshOverride = { _ in
            providerRefreshes += 1
        }
        store._test_codexCreditsLoaderOverride = {
            CreditsSnapshot(remaining: 25, events: [], updatedAt: Date())
        }
        store._test_tokenUsageRefreshOverride = { provider, force in
            guard force else { return }
            await tokenGate.run(
                provider: provider,
                force: force,
                interaction: ProviderInteractionContext.current,
                refreshPhase: ProviderRefreshContext.current,
                browserRetryAllowed: false)
        }
        store._test_forcedRefreshEnrichmentWaitObserver = {
            didObserveWait = true
        }
        defer {
            store._test_providerRefreshOverride = nil
            store._test_codexCreditsLoaderOverride = nil
            store._test_tokenUsageRefreshOverride = nil
            store._test_forcedRefreshEnrichmentWaitObserver = nil
        }

        await store.refresh(enrichmentMode: .forcedBackground)
        #expect(await tokenGate.waitUntilStarted(count: 1))
        settings.costUsageEnabled = false

        let boundaryRefreshAt = Date(timeIntervalSince1970: 12345)
        let boundaryTask = Task { @MainActor in
            await store.runResetBoundaryRefresh(boundaryRefreshAt: boundaryRefreshAt)
        }
        for _ in 0..<100 where !didObserveWait {
            await Task.yield()
        }

        #expect(didObserveWait)
        #expect(providerRefreshes == 1)
        #expect(store.attemptedResetBoundaryRefreshes.contains(boundaryRefreshAt))

        await tokenGate.resumeNext()
        await boundaryTask.value

        #expect(providerRefreshes == 2)
        #expect(store.attemptedResetBoundaryRefreshes.contains(boundaryRefreshAt))
        #expect(!store.hasForcedRefreshEnrichmentInFlight)
    }

    @Test
    @MainActor
    func `boundary refresh does not reschedule unchanged stale snapshot`() async {
        let settings = testSettingsStore(suiteName: "UsageStoreResetBoundaryRefreshTests-no-duplicate")
        settings.refreshFrequency = .oneMinute
        settings.statusChecksEnabled = false
        settings.costUsageEnabled = false
        settings.openAIWebAccessEnabled = false
        settings.codexCookieSource = .off
        for provider in UsageProvider.allCases {
            guard let metadata = ProviderRegistry.shared.metadata[provider] else { continue }
            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: provider == .codex)
        }
        let store = UsageStore(
            fetcher: UsageFetcher(environment: [:]),
            browserDetection: BrowserDetection(cacheTTL: 0),
            settings: settings,
            startupBehavior: .testing)
        let now = Date()
        let boundaryRefreshAt = now.addingTimeInterval(1)
        store.snapshots[.codex] = Self.snapshot(
            updatedAt: now.addingTimeInterval(-60),
            primaryResetsAt: boundaryRefreshAt.addingTimeInterval(-UsageStore.resetBoundaryRefreshGraceSeconds))
        var providerRefreshes = 0
        store._test_providerRefreshOverride = { _ in
            providerRefreshes += 1
        }
        defer {
            store._test_providerRefreshOverride = nil
            store.cancelResetBoundaryRefresh()
        }

        await store.runResetBoundaryRefresh(boundaryRefreshAt: boundaryRefreshAt)

        #expect(providerRefreshes == 1)
        #expect(store.attemptedResetBoundaryRefreshes.contains(boundaryRefreshAt))
        #expect(store.resetBoundaryRefreshTask == nil)
        #expect(store.scheduledResetBoundaryRefreshAt == nil)
    }

    @Test
    func `ignores reset boundary after normal poll`() {
        let now = Date(timeIntervalSince1970: 3000)
        let resetsAt = now.addingTimeInterval(40 * 60)
        let snapshot = Self.snapshot(updatedAt: now, primaryResetsAt: resetsAt)

        let refreshAt = UsageStore.nextResetBoundaryRefreshDate(
            snapshots: [.codex: snapshot],
            normalRefreshInterval: 30 * 60,
            now: now)

        #expect(refreshAt == nil)
    }

    @Test
    func `ignores already refreshed reset boundary`() {
        let now = Date(timeIntervalSince1970: 4000)
        let resetsAt = now.addingTimeInterval(-3 * 60)
        let snapshot = Self.snapshot(updatedAt: now, primaryResetsAt: resetsAt)

        let refreshAt = UsageStore.nextResetBoundaryRefreshDate(
            snapshots: [.codex: snapshot],
            normalRefreshInterval: 30 * 60,
            now: now)

        #expect(refreshAt == nil)
    }

    @Test
    func `uses earliest boundary across secondary and extra windows`() {
        let now = Date(timeIntervalSince1970: 5000)
        let secondaryResetsAt = now.addingTimeInterval(8 * 60)
        let extraResetsAt = now.addingTimeInterval(4 * 60)
        let snapshot = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 10,
                windowMinutes: 300,
                resetsAt: now.addingTimeInterval(20 * 60),
                resetDescription: nil),
            secondary: RateWindow(
                usedPercent: 80,
                windowMinutes: 10080,
                resetsAt: secondaryResetsAt,
                resetDescription: nil),
            tertiary: nil,
            extraRateWindows: [
                NamedRateWindow(
                    id: "extra",
                    title: "Extra",
                    window: RateWindow(
                        usedPercent: 50,
                        windowMinutes: 60,
                        resetsAt: extraResetsAt,
                        resetDescription: nil)),
            ],
            updatedAt: now)

        let refreshAt = UsageStore.nextResetBoundaryRefreshDate(
            snapshots: [.codex: snapshot],
            normalRefreshInterval: 30 * 60,
            now: now)

        #expect(refreshAt == extraResetsAt.addingTimeInterval(UsageStore.resetBoundaryRefreshGraceSeconds))
    }

    @Test
    func `manual refresh cadence does not schedule boundary refresh`() {
        let now = Date(timeIntervalSince1970: 6000)
        let snapshot = Self.snapshot(
            updatedAt: now,
            primaryResetsAt: now.addingTimeInterval(10 * 60))

        let refreshAt = UsageStore.nextResetBoundaryRefreshDate(
            snapshots: [.codex: snapshot],
            normalRefreshInterval: nil,
            now: now)

        #expect(refreshAt == nil)
    }

    private static func snapshot(updatedAt: Date, primaryResetsAt: Date) -> UsageSnapshot {
        UsageSnapshot(
            primary: RateWindow(
                usedPercent: 100,
                windowMinutes: 300,
                resetsAt: primaryResetsAt,
                resetDescription: nil),
            secondary: nil,
            tertiary: nil,
            updatedAt: updatedAt)
    }
}
