import CodexBarCore
import Foundation
import Testing
@testable import CodexBar

struct CodexResetBackfillSemanticsTests {
    @Test
    func `merged reset cache preserves semantic lanes across swapped snapshots`() throws {
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let sessionReset = now.addingTimeInterval(4 * 60 * 60)
        let weeklyReset = now.addingTimeInterval(4 * 24 * 60 * 60)
        let canonicalSessionCache = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 17,
                windowMinutes: 300,
                resetsAt: sessionReset,
                resetDescription: nil),
            secondary: nil,
            updatedAt: now.addingTimeInterval(-20))
        let swappedWeeklyCache = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 63,
                windowMinutes: 10080,
                resetsAt: weeklyReset,
                resetDescription: nil),
            secondary: nil,
            updatedAt: now.addingTimeInterval(-10))

        let merged = try #require(UsageStore.codexMergedResetBackfillSnapshot(
            [canonicalSessionCache, swappedWeeklyCache],
            now: now))

        #expect(merged.primary?.usedPercent == 17)
        #expect(merged.primary?.windowMinutes == 300)
        #expect(merged.primary?.resetsAt == sessionReset)
        #expect(merged.secondary?.usedPercent == 63)
        #expect(merged.secondary?.windowMinutes == 10080)
        #expect(merged.secondary?.resetsAt == weeklyReset)
    }

    @Test
    func `reset backfill preserves a monthly primary window`() {
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let monthlyReset = now.addingTimeInterval(11 * 24 * 60 * 60)
        let weeklyReset = now.addingTimeInterval(3 * 24 * 60 * 60)
        let fresh = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 41,
                windowMinutes: 43200,
                resetsAt: monthlyReset,
                resetDescription: nil),
            secondary: RateWindow(
                usedPercent: 12,
                windowMinutes: 10080,
                resetsAt: nil,
                resetDescription: nil),
            updatedAt: now)
        let cached = UsageSnapshot(
            primary: nil,
            secondary: RateWindow(
                usedPercent: 12,
                windowMinutes: 10080,
                resetsAt: weeklyReset,
                resetDescription: nil),
            updatedAt: now.addingTimeInterval(-60))

        let backfilled = UsageStore.codexBackfillingResetWindows(fresh, from: cached)

        #expect(backfilled.primary?.windowMinutes == 43200)
        #expect(backfilled.primary?.usedPercent == 41)
        #expect(backfilled.primary?.resetsAt == monthlyReset)
        #expect(backfilled.secondary?.windowMinutes == 10080)
        #expect(backfilled.secondary?.resetsAt == weeklyReset)
    }

    @Test
    func `reset backfill does not overwrite a monthly primary with a stale cached session window`() {
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let monthlyReset = now.addingTimeInterval(11 * 24 * 60 * 60)
        let fresh = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 41,
                windowMinutes: 43200,
                resetsAt: monthlyReset,
                resetDescription: nil),
            secondary: nil,
            updatedAt: now)
        let cached = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 88,
                windowMinutes: 300,
                resetsAt: now.addingTimeInterval(2 * 60 * 60),
                resetDescription: nil),
            secondary: nil,
            updatedAt: now.addingTimeInterval(-60))

        let backfilled = UsageStore.codexBackfillingResetWindows(fresh, from: cached)

        #expect(backfilled.primary?.windowMinutes == 43200)
        #expect(backfilled.primary?.usedPercent == 41)
        #expect(backfilled.primary?.resetsAt == monthlyReset)
    }

    @Test
    func `merged reset cache keeps a monthly reset for a fresh reset-less monthly primary`() throws {
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let monthlyReset = now.addingTimeInterval(11 * 24 * 60 * 60)
        let cached = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 41,
                windowMinutes: 43200,
                resetsAt: monthlyReset,
                resetDescription: nil),
            secondary: nil,
            updatedAt: now.addingTimeInterval(-60))
        let fresh = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 41,
                windowMinutes: 43200,
                resetsAt: nil,
                resetDescription: nil),
            secondary: nil,
            updatedAt: now)

        let merged = try #require(UsageStore.codexMergedResetBackfillSnapshot([cached, fresh], now: now))
        let backfilled = UsageStore.codexBackfillingResetWindows(fresh, from: merged)

        #expect(merged.primary?.windowMinutes == 43200)
        #expect(merged.primary?.resetsAt == monthlyReset)
        #expect(backfilled.primary?.windowMinutes == 43200)
        #expect(backfilled.primary?.resetsAt == monthlyReset)
    }

    @Test
    func `merged reset cache prefers a newer monthly primary over a stale session candidate`() throws {
        let now = Date(timeIntervalSince1970: 1_800_000_000)
        let monthlyReset = now.addingTimeInterval(11 * 24 * 60 * 60)
        let weeklyReset = now.addingTimeInterval(3 * 24 * 60 * 60)
        let staleSession = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 17,
                windowMinutes: 300,
                resetsAt: now.addingTimeInterval(4 * 60 * 60),
                resetDescription: nil),
            secondary: nil,
            updatedAt: now.addingTimeInterval(-100))
        let monthlyShape = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 41,
                windowMinutes: 43200,
                resetsAt: monthlyReset,
                resetDescription: nil),
            secondary: RateWindow(
                usedPercent: 12,
                windowMinutes: 10080,
                resetsAt: weeklyReset,
                resetDescription: nil),
            updatedAt: now.addingTimeInterval(-50))

        let merged = try #require(UsageStore.codexMergedResetBackfillSnapshot(
            [staleSession, monthlyShape],
            now: now))

        #expect(merged.primary?.windowMinutes == 43200)
        #expect(merged.primary?.resetsAt == monthlyReset)
        #expect(merged.secondary?.windowMinutes == 10080)
        #expect(merged.secondary?.resetsAt == weeklyReset)
    }
}

extension CodexAccountScopedRefreshTests {
    @Test
    func `stacked email only rows use neither prior baselines nor reset backfills`() async throws {
        let fixture = try self.makeEmailOnlyStackedFixture(
            suite: "CodexResetBackfillSemanticsTests-email-only-stacked")
        defer { fixture.cleanup() }

        let now = Date()
        let weeklyReset = now.addingTimeInterval(3 * 24 * 60 * 60)
        let targetPrior = self.codexWeeklySnapshot(
            email: fixture.target.email,
            weeklyUsedPercent: 74,
            weeklyReset: weeklyReset,
            updatedAt: now.addingTimeInterval(-60))
        let siblingPrior = self.codexWeeklySnapshot(
            email: fixture.sibling.email,
            weeklyUsedPercent: 28,
            weeklyReset: weeklyReset,
            updatedAt: now.addingTimeInterval(-60))
        let priorRows = [
            CodexAccountUsageSnapshot(
                account: fixture.target,
                snapshot: targetPrior,
                error: nil,
                sourceLabel: "cached-target"),
            CodexAccountUsageSnapshot(
                account: fixture.sibling,
                snapshot: siblingPrior,
                error: nil,
                sourceLabel: "cached-sibling"),
        ]
        let snapshotStore = RecordingCodexAccountUsageSnapshotStore(initialSnapshots: priorRows)
        let store = self.makeCodexWeeklyPublicationStore(
            settings: fixture.settings,
            suite: "CodexResetBackfillSemanticsTests-email-only-stacked",
            snapshotStore: snapshotStore)
        #expect(store.codexLimitResetOwnerKey(
            forVisibleAccount: fixture.target,
            visibleAccounts: [fixture.target, fixture.sibling]) == nil)

        let initialLow = self.codexWeeklySnapshot(
            email: fixture.target.email,
            weeklyUsedPercent: 0.2,
            weeklyReset: weeklyReset,
            updatedAt: now.addingTimeInterval(-40))
        let confirmedLow = self.codexWeeklySnapshot(
            email: fixture.target.email,
            weeklyUsedPercent: 0.4,
            weeklyReset: weeklyReset,
            updatedAt: now.addingTimeInterval(-30))
        let partial = self.codexWeeklySnapshot(
            email: fixture.target.email,
            weeklyUsedPercent: nil,
            weeklyReset: nil,
            updatedAt: now.addingTimeInterval(-20),
            sessionUsedPercent: 31)
        let targetLoader = SequencedCodexSnapshotLoader(steps: [
            .success(initialLow),
            .success(confirmedLow),
            .success(partial),
        ])
        let siblingCurrent = self.codexWeeklySnapshot(
            email: fixture.sibling.email,
            weeklyUsedPercent: 29,
            weeklyReset: weeklyReset,
            updatedAt: now.addingTimeInterval(-10))
        let targetHomePath = fixture.targetHome.path
        let siblingHomePath = fixture.siblingHome.path
        self.installContextualCodexProvider(on: store) { context in
            switch context.env["CODEX_HOME"] {
            case targetHomePath:
                try await targetLoader.load()
            case siblingHomePath:
                siblingCurrent
            default:
                throw TestRefreshError(message: "Unexpected CODEX_HOME routing")
            }
        }

        await store.refreshCodexVisibleAccountsForMenu()

        let confirmedTarget = try #require(store.codexAccountSnapshots.first {
            $0.account.storedAccountID == fixture.target.storedAccountID
        }?.snapshot)
        #expect(confirmedTarget.updatedAt == confirmedLow.updatedAt)
        #expect(confirmedTarget.secondary?.usedPercent == 0.4)

        await store.refreshCodexVisibleAccountsForMenu()

        let partialTarget = try #require(store.codexAccountSnapshots.first {
            $0.account.storedAccountID == fixture.target.storedAccountID
        }?.snapshot)
        #expect(await targetLoader.callCount == 3)
        #expect(partialTarget.updatedAt == partial.updatedAt)
        #expect(partialTarget.primary?.usedPercent == 31)
        #expect(partialTarget.secondary == nil)
    }

    private func makeEmailOnlyStackedFixture(suite: String) throws -> EmailOnlyStackedFixture {
        let settings = self.makeSettingsStore(suite: suite)
        settings.refreshFrequency = .manual
        settings.codexCookieSource = .off
        settings.multiAccountMenuLayout = .stacked

        let targetID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-424242424242"))
        let siblingID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-434343434343"))
        let targetHome = FileManager.default.temporaryDirectory
            .appendingPathComponent("codex-email-only-target-\(UUID().uuidString)", isDirectory: true)
        let siblingHome = FileManager.default.temporaryDirectory
            .appendingPathComponent("codex-email-only-sibling-\(UUID().uuidString)", isDirectory: true)
        try Self.writeCodexAuthFile(
            homeURL: targetHome,
            email: "email-only-target@example.com",
            plan: "Pro")
        try Self.writeCodexAuthFile(
            homeURL: siblingHome,
            email: "email-only-sibling@example.com",
            plan: "Pro")
        let targetFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: targetHome.path))
        let siblingFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: siblingHome.path))
        let targetAccount = ManagedCodexAccount(
            id: targetID,
            email: "email-only-target@example.com",
            authFingerprint: targetFingerprint,
            managedHomePath: targetHome.path,
            createdAt: 1,
            updatedAt: 2,
            lastAuthenticatedAt: 2)
        let siblingAccount = ManagedCodexAccount(
            id: siblingID,
            email: "email-only-sibling@example.com",
            authFingerprint: siblingFingerprint,
            managedHomePath: siblingHome.path,
            createdAt: 1,
            updatedAt: 2,
            lastAuthenticatedAt: 2)
        let accountStoreURL = try self.makeManagedAccountStoreURL(accounts: [targetAccount, siblingAccount])
        settings._test_managedCodexAccountStoreURL = accountStoreURL
        settings.codexActiveSource = .managedAccount(id: targetID)

        let projection = settings.codexVisibleAccountProjection
        let target = try #require(projection.visibleAccounts.first { $0.storedAccountID == targetID })
        let sibling = try #require(projection.visibleAccounts.first { $0.storedAccountID == siblingID })
        #expect(target.workspaceAccountID == nil)
        #expect(sibling.workspaceAccountID == nil)

        return EmailOnlyStackedFixture(
            settings: settings,
            target: target,
            sibling: sibling,
            targetHome: targetHome,
            siblingHome: siblingHome,
            accountStoreURL: accountStoreURL)
    }
}

private struct EmailOnlyStackedFixture {
    let settings: SettingsStore
    let target: CodexVisibleAccount
    let sibling: CodexVisibleAccount
    let targetHome: URL
    let siblingHome: URL
    let accountStoreURL: URL

    @MainActor
    func cleanup() {
        self.settings._test_managedCodexAccountStoreURL = nil
        try? FileManager.default.removeItem(at: self.accountStoreURL)
        try? FileManager.default.removeItem(at: self.targetHome)
        try? FileManager.default.removeItem(at: self.siblingHome)
    }
}
