import CodexBarCore
import Foundation
import Testing
@testable import CodexBar

@MainActor
struct CodexConsumerProjectionTests {
    @Test
    func `live card projection compacts weekly lanes and attaches dashboard extras`() {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-live-card")
        let now = Date(timeIntervalSince1970: 1_700_000_000)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: nil,
                secondary: RateWindow(
                    usedPercent: 25,
                    windowMinutes: 10080,
                    resetsAt: now.addingTimeInterval(3600),
                    resetDescription: nil),
                updatedAt: now),
            provider: .codex)
        store.credits = CreditsSnapshot(remaining: 42, events: [], updatedAt: now)
        store.openAIDashboard = OpenAIDashboardSnapshot(
            signedInEmail: "codex@example.com",
            codeReviewRemainingPercent: 88,
            codeReviewLimit: RateWindow(
                usedPercent: 12,
                windowMinutes: nil,
                resetsAt: now.addingTimeInterval(7200),
                resetDescription: nil),
            creditEvents: [],
            dailyBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 3)],
            usageBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 4)],
            creditsPurchaseURL: "https://chatgpt.com/settings/billing",
            updatedAt: now)
        store.openAIDashboardAttachmentAuthorized = true
        store.openAIDashboardRequiresLogin = false

        let projection = store.codexConsumerProjection(surface: .liveCard, now: now)

        #expect(projection.visibleRateLanes == [.weekly])
        #expect(projection.planUtilizationLanes.map(\.role.rawValue) == ["weekly"])
        #expect(projection.dashboardVisibility == .attached)
        #expect(projection.supplementalMetrics == [.codeReview])
        #expect(projection.remainingPercent(for: .codeReview) == 88)
        #expect(projection.credits?.remaining == 42)
        #expect(projection.canShowBuyCredits)
        #expect(projection.hasUsageBreakdown)
        #expect(projection.hasCreditsHistory)
    }

    @Test
    func `display only dashboard stays visible without attached extras`() {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-display-only")
        let now = Date(timeIntervalSince1970: 1_700_000_000)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 15,
                    windowMinutes: 300,
                    resetsAt: now.addingTimeInterval(1800),
                    resetDescription: nil),
                secondary: RateWindow(
                    usedPercent: 30,
                    windowMinutes: 10080,
                    resetsAt: now.addingTimeInterval(3600),
                    resetDescription: nil),
                updatedAt: now),
            provider: .codex)
        store.openAIDashboard = OpenAIDashboardSnapshot(
            signedInEmail: "codex@example.com",
            codeReviewRemainingPercent: 66,
            creditEvents: [],
            dailyBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 3)],
            usageBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 4)],
            creditsPurchaseURL: "https://chatgpt.com/settings/billing",
            updatedAt: now)
        store.openAIDashboardAttachmentAuthorized = false
        store.openAIDashboardRequiresLogin = false

        let projection = store.codexConsumerProjection(surface: .liveCard, now: now)

        #expect(projection.dashboardVisibility == .displayOnly)
        #expect(projection.supplementalMetrics.isEmpty)
        #expect(projection.canShowBuyCredits)
        #expect(!projection.hasUsageBreakdown)
        #expect(!projection.hasCreditsHistory)
    }

    @Test
    func `override card projection does not pull live codex adjuncts`() {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-override")
        let now = Date(timeIntervalSince1970: 1_700_000_000)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 18,
                    windowMinutes: 300,
                    resetsAt: now.addingTimeInterval(1800),
                    resetDescription: nil),
                secondary: nil,
                updatedAt: now),
            provider: .codex)
        store.credits = CreditsSnapshot(remaining: 42, events: [], updatedAt: now)
        store.lastCreditsError = "Frame load interrupted"
        store.openAIDashboard = OpenAIDashboardSnapshot(
            signedInEmail: "codex@example.com",
            codeReviewRemainingPercent: 88,
            creditEvents: [],
            dailyBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 3)],
            usageBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 4)],
            creditsPurchaseURL: "https://chatgpt.com/settings/billing",
            updatedAt: now)
        store.openAIDashboardAttachmentAuthorized = true
        store.openAIDashboardRequiresLogin = false
        store._setErrorForTesting("Live codex error", provider: .codex)

        let overrideSnapshot = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 55,
                windowMinutes: 300,
                resetsAt: now.addingTimeInterval(1200),
                resetDescription: nil),
            secondary: nil,
            updatedAt: now)

        let projection = store.codexConsumerProjection(
            surface: .overrideCard,
            snapshotOverride: overrideSnapshot,
            errorOverride: "Override error",
            now: now)

        #expect(projection.visibleRateLanes == [.session])
        #expect(projection.dashboardVisibility == .hidden)
        #expect(projection.credits == nil)
        #expect(projection.supplementalMetrics.isEmpty)
        #expect(!projection.canShowBuyCredits)
        #expect(!projection.hasUsageBreakdown)
        #expect(!projection.hasCreditsHistory)
        #expect(projection.userFacingErrors.usage == "Override error")
        #expect(projection.userFacingErrors.credits == nil)
        #expect(projection.userFacingErrors.dashboard == nil)
    }

    @Test
    func `menu bar projection flags credits fallback on exhaustion`() {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-menu-bar")
        let now = Date(timeIntervalSince1970: 1_700_000_000)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 100,
                    windowMinutes: 300,
                    resetsAt: now.addingTimeInterval(1800),
                    resetDescription: nil),
                secondary: RateWindow(
                    usedPercent: 40,
                    windowMinutes: 10080,
                    resetsAt: now.addingTimeInterval(3600),
                    resetDescription: nil),
                updatedAt: now),
            provider: .codex)
        store.credits = CreditsSnapshot(remaining: 80, events: [], updatedAt: now)

        let projection = store.codexConsumerProjection(surface: .menuBar, now: now)

        #expect(projection.menuBarFallback == .creditsBalance)
    }

    @Test
    func `live card projection keeps buy credits available without dashboard purchase URL`() {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-buy-credits")
        let now = Date(timeIntervalSince1970: 1_700_000_000)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 20,
                    windowMinutes: 300,
                    resetsAt: now.addingTimeInterval(1800),
                    resetDescription: nil),
                secondary: nil,
                updatedAt: now),
            provider: .codex)
        store.credits = CreditsSnapshot(remaining: 42, events: [], updatedAt: now)
        store.openAIDashboardAttachmentAuthorized = false
        store.openAIDashboardRequiresLogin = false

        let projection = store.codexConsumerProjection(surface: .liveCard, now: now)

        #expect(projection.canShowBuyCredits)
    }

    @Test
    func `menu bar projection keeps credits fallback when credits load before usage`() {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-menu-bar-credits-only")
        let now = Date(timeIntervalSince1970: 1_700_000_000)

        store._setSnapshotForTesting(nil, provider: .codex)
        store.credits = CreditsSnapshot(remaining: 80, events: [], updatedAt: now)

        let projection = store.codexConsumerProjection(surface: .menuBar, now: now)

        #expect(projection.menuBarFallback == .creditsBalance)
        #expect(!projection.hasExhaustedRateLane)
    }

    @Test
    func `projection prefers monthly credit limit remaining over zero balance`() {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-monthly-credit-limit")
        let now = Date(timeIntervalSince1970: 1_700_000_000)

        store._setSnapshotForTesting(nil, provider: .codex)
        store.credits = CreditsSnapshot(
            remaining: 0,
            events: [],
            updatedAt: now,
            codexCreditLimit: CodexCreditLimitSnapshot(
                used: 7761,
                limit: 100_000,
                remainingPercent: 92.239,
                resetsAt: nil,
                updatedAt: now))

        let projection = store.codexConsumerProjection(surface: .widget, now: now)

        #expect(projection.credits?.remaining == 92239)
    }

    @Test
    func `exhausted weekly lane caps session display until weekly reset`() throws {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-caps-session")
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let sessionReset = now.addingTimeInterval(3 * 3600)
        let weeklyReset = now.addingTimeInterval(4 * 24 * 3600)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 1,
                    windowMinutes: 300,
                    resetsAt: sessionReset,
                    resetDescription: nil),
                secondary: RateWindow(
                    usedPercent: 157,
                    windowMinutes: 10080,
                    resetsAt: weeklyReset,
                    resetDescription: nil),
                updatedAt: now),
            provider: .codex)

        let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
        let session = try #require(projection.rateWindow(for: .session))
        let weekly = try #require(projection.rateWindow(for: .weekly))

        #expect(session.remainingPercent == 0)
        #expect(session.resetsAt == weeklyReset)
        #expect(weekly.remainingPercent == 0)
        #expect(weekly.resetsAt == weeklyReset)
        #expect(projection.planUtilizationLanes.first?.window.usedPercent == 1)
    }

    @Test
    func `exhausted weekly lane retargets session reset when session is also exhausted`() throws {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-caps-both-exhausted")
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let sessionReset = now.addingTimeInterval(42 * 60)
        let weeklyReset = now.addingTimeInterval(4 * 24 * 3600)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 100,
                    windowMinutes: 300,
                    resetsAt: sessionReset,
                    resetDescription: nil),
                secondary: RateWindow(
                    usedPercent: 157,
                    windowMinutes: 10080,
                    resetsAt: weeklyReset,
                    resetDescription: nil),
                updatedAt: now),
            provider: .codex)

        let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
        let session = try #require(projection.rateWindow(for: .session))

        #expect(session.remainingPercent == 0)
        #expect(session.resetsAt == weeklyReset)
        #expect(session.resetsAt != sessionReset)
    }

    @Test
    func `both exhausted lanes use the later session reset`() throws {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-session-reset-binds-later")
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let weeklyReset = now.addingTimeInterval(60 * 60)
        let sessionReset = now.addingTimeInterval(4 * 60 * 60)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 100,
                    windowMinutes: 300,
                    resetsAt: sessionReset,
                    resetDescription: "session reset"),
                secondary: RateWindow(
                    usedPercent: 100,
                    windowMinutes: 10080,
                    resetsAt: weeklyReset,
                    resetDescription: "weekly reset"),
                updatedAt: now),
            provider: .codex)

        let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
        let session = try #require(projection.rateWindow(for: .session))

        #expect(session.remainingPercent == 0)
        #expect(session.resetsAt == sessionReset)
        #expect(session.resetDescription == "session reset")
    }

    @Test
    func `both exhausted lanes keep effective reset unknown when session reset is unknown`() throws {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-session-reset-unknown")
        let now = Date(timeIntervalSince1970: 1_800_000_000)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 100,
                    windowMinutes: 300,
                    resetsAt: nil,
                    resetDescription: nil),
                secondary: RateWindow(
                    usedPercent: 100,
                    windowMinutes: 10080,
                    resetsAt: now.addingTimeInterval(60 * 60),
                    resetDescription: "weekly reset"),
                updatedAt: now),
            provider: .codex)

        let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
        let session = try #require(projection.rateWindow(for: .session))

        #expect(session.remainingPercent == 0)
        #expect(session.resetsAt == nil)
        #expect(session.resetDescription == nil)
    }

    @Test
    func `exhausted weekly lane leaves session reset unknown when weekly reset is unknown`() throws {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-caps-unknown-reset")
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let sessionReset = now.addingTimeInterval(42 * 60)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 1,
                    windowMinutes: 300,
                    resetsAt: sessionReset,
                    resetDescription: "in 42m"),
                secondary: RateWindow(
                    usedPercent: 100,
                    windowMinutes: 10080,
                    resetsAt: nil,
                    resetDescription: nil),
                updatedAt: now),
            provider: .codex)

        let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
        let session = try #require(projection.rateWindow(for: .session))

        #expect(session.remainingPercent == 0)
        #expect(session.resetsAt == nil)
        #expect(session.resetDescription == nil)
    }

    @Test
    func `weekly cap lifts after weekly reset even with stale snapshot timestamp`() throws {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-cap-stale-snapshot")
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let snapshotCapturedAt = now.addingTimeInterval(-2 * 3600)
        let sessionReset = now.addingTimeInterval(3 * 3600)
        let weeklyReset = now.addingTimeInterval(-3600)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 1,
                    windowMinutes: 300,
                    resetsAt: sessionReset,
                    resetDescription: nil),
                secondary: RateWindow(
                    usedPercent: 100,
                    windowMinutes: 10080,
                    resetsAt: weeklyReset,
                    resetDescription: nil),
                updatedAt: snapshotCapturedAt),
            provider: .codex)
        store.credits = CreditsSnapshot(remaining: 80, events: [], updatedAt: now)

        let projection = store.codexConsumerProjection(surface: .menuBar, now: now)
        let session = try #require(projection.rateWindow(for: .session))

        #expect(session.remainingPercent == 99)
        #expect(session.resetsAt == sessionReset)
        #expect(projection.menuBarFallback == .none)
    }

    @Test
    func `weekly cap does not alter session display when weekly has reset`() throws {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-reset-session-uncapped")
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let sessionReset = now.addingTimeInterval(3 * 3600)
        let weeklyReset = now.addingTimeInterval(-3600)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 1,
                    windowMinutes: 300,
                    resetsAt: sessionReset,
                    resetDescription: nil),
                secondary: RateWindow(
                    usedPercent: 100,
                    windowMinutes: 10080,
                    resetsAt: weeklyReset,
                    resetDescription: nil),
                updatedAt: now),
            provider: .codex)

        let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
        let session = try #require(projection.rateWindow(for: .session))

        #expect(session.remainingPercent == 99)
        #expect(session.resetsAt == sessionReset)
    }

    @Test
    func `weekly cap lifts at the weekly reset boundary`() throws {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-reset-boundary")
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let sessionReset = now.addingTimeInterval(3 * 3600)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 1,
                    windowMinutes: 300,
                    resetsAt: sessionReset,
                    resetDescription: nil),
                secondary: RateWindow(
                    usedPercent: 100,
                    windowMinutes: 10080,
                    resetsAt: now,
                    resetDescription: nil),
                updatedAt: now.addingTimeInterval(-3600)),
            provider: .codex)

        let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
        let session = try #require(projection.rateWindow(for: .session))

        #expect(session.remainingPercent == 99)
        #expect(session.resetsAt == sessionReset)
    }

    @Test
    func `thirty day primary window maps to a monthly lane instead of session`() {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-30day-primary")
        let now = Date(timeIntervalSince1970: 1_800_000_000)

        store._setSnapshotForTesting(
            UsageSnapshot(
                primary: RateWindow(
                    usedPercent: 55,
                    windowMinutes: 43200,
                    resetsAt: now.addingTimeInterval(24 * 86400),
                    resetDescription: nil),
                secondary: RateWindow(
                    usedPercent: 5,
                    windowMinutes: 10080,
                    resetsAt: now.addingTimeInterval(7 * 86400),
                    resetDescription: nil),
                updatedAt: now),
            provider: .codex)

        let projection = store.codexConsumerProjection(surface: .liveCard, now: now)

        #expect(projection.visibleRateLanes == [.monthly, .weekly])
        #expect(projection.rateWindow(for: .session) == nil)
        #expect(projection.rateWindow(for: .monthly)?.windowMinutes == 43200)
        #expect(projection.planUtilizationLanes.map(\.role.rawValue) == ["weekly", "monthly"])
    }

    @Test
    func `automatic menu bar metric prefers the weekly window over a thirty day primary`() {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-30day-automatic")
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let snapshot = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 55,
                windowMinutes: 43200,
                resetsAt: now.addingTimeInterval(24 * 86400),
                resetDescription: nil),
            secondary: RateWindow(
                usedPercent: 5,
                windowMinutes: 10080,
                resetsAt: now.addingTimeInterval(7 * 86400),
                resetDescription: nil),
            updatedAt: now)

        let projection = store.codexConsumerProjection(
            surface: .menuBar,
            snapshotOverride: snapshot,
            now: now)

        #expect(projection.automaticMenuBarWindow()?.windowMinutes == 10080)
        #expect(store.codexMenuBarMetricWindow(snapshot: snapshot, now: now)?.windowMinutes == 10080)
    }

    @Test
    func `automatic menu bar metric keeps the standard five hour session primary`() {
        let store = self.makeStore(suite: "CodexConsumerProjectionTests-standard-automatic")
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let snapshot = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 55,
                windowMinutes: 300,
                resetsAt: now.addingTimeInterval(3 * 3600),
                resetDescription: nil),
            secondary: RateWindow(
                usedPercent: 5,
                windowMinutes: 10080,
                resetsAt: now.addingTimeInterval(7 * 86400),
                resetDescription: nil),
            updatedAt: now)

        let window = store.codexMenuBarMetricWindow(snapshot: snapshot, now: now)

        #expect(window?.windowMinutes == 300)
    }

    private func makeStore(suite: String) -> UsageStore {
        let defaults = UserDefaults(suiteName: suite)!
        defaults.removePersistentDomain(forName: suite)
        let configStore = testConfigStore(suiteName: suite)
        let settings = SettingsStore(
            userDefaults: defaults,
            configStore: configStore,
            zaiTokenStore: NoopZaiTokenStore(),
            syntheticTokenStore: NoopSyntheticTokenStore(),
            codexCookieStore: InMemoryCookieHeaderStore(),
            claudeCookieStore: InMemoryCookieHeaderStore(),
            cursorCookieStore: InMemoryCookieHeaderStore(),
            opencodeCookieStore: InMemoryCookieHeaderStore(),
            factoryCookieStore: InMemoryCookieHeaderStore(),
            minimaxCookieStore: InMemoryMiniMaxCookieStore(),
            minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),
            kimiTokenStore: InMemoryKimiTokenStore(),
            augmentCookieStore: InMemoryCookieHeaderStore(),
            ampCookieStore: InMemoryCookieHeaderStore(),
            copilotTokenStore: InMemoryCopilotTokenStore(),
            tokenAccountStore: InMemoryTokenAccountStore())

        return UsageStore(
            fetcher: UsageFetcher(environment: [:]),
            browserDetection: BrowserDetection(cacheTTL: 0),
            settings: settings,
            startupBehavior: .testing)
    }
}
