import CodexBarCore
import Foundation
import Testing
@testable import CodexBar

struct UsageStorePlanUtilizationClaudeIdentityBoundaryTests {
    @MainActor
    @Test
    func `claude history without identity falls back to last resolved account`() async {
        let store = UsageStorePlanUtilizationTests.makeStore()
        let snapshot = UsageSnapshot(
            primary: RateWindow(usedPercent: 10, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
            secondary: RateWindow(usedPercent: 20, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),
            updatedAt: Date(),
            identity: ProviderIdentitySnapshot(
                providerID: .claude,
                accountEmail: "alice@example.com",
                accountOrganization: nil,
                loginMethod: "max"))
        store._setSnapshotForTesting(snapshot, provider: .claude)

        await store.recordPlanUtilizationHistorySample(
            provider: .claude,
            snapshot: snapshot,
            now: Date(timeIntervalSince1970: 1_700_000_000))

        let identitylessSnapshot = UsageSnapshot(
            primary: snapshot.primary,
            secondary: snapshot.secondary,
            updatedAt: snapshot.updatedAt)
        store._setSnapshotForTesting(identitylessSnapshot, provider: .claude)

        let history = store.planUtilizationHistory(for: .claude)
        #expect(findSeries(history, name: .session, windowMinutes: 300)?.entries.last?.usedPercent == 10)
        #expect(findSeries(history, name: .weekly, windowMinutes: 10080)?.entries.last?.usedPercent == 20)
    }

    @MainActor
    @Test
    func `established account accepts same owner after access token rotation`() async throws {
        let store = UsageStorePlanUtilizationTests.makeStore()
        let owner = String(repeating: "a", count: 64)
        let accountIdentity = UsageStore._activeClaudeAccountIdentityForTesting("uuid-A")
        let start = Date(timeIntervalSince1970: 1_700_000_000)

        await store.recordPlanUtilizationHistorySample(
            provider: .claude,
            snapshot: self.snapshot(usedPercent: 30),
            claudeOAuthPersistentRefHash: "account-a-ref",
            claudeOAuthHistoryOwnerIdentifier: owner,
            claudeOAuthActiveAccountObservation: .stable(identity: accountIdentity),
            isClaudeOAuthSample: true,
            now: start)
        await store.recordPlanUtilizationHistorySample(
            provider: .claude,
            snapshot: self.snapshot(usedPercent: 30),
            claudeOAuthPersistentRefHash: "account-a-ref",
            claudeOAuthHistoryOwnerIdentifier: owner,
            claudeOAuthActiveAccountObservation: .stable(identity: accountIdentity),
            isClaudeOAuthSample: true,
            now: start.addingTimeInterval(30 * 60))
        await store.recordPlanUtilizationHistorySample(
            provider: .claude,
            snapshot: self.snapshot(usedPercent: 50),
            claudeOAuthHistoryOwnerIdentifier: owner,
            claudeOAuthKeychainCredentialMismatch: true,
            claudeOAuthActiveAccountObservation: .stable(identity: accountIdentity),
            isClaudeOAuthSample: true,
            now: start.addingTimeInterval(2 * 60 * 60))

        let key = try #require(
            UsageStore._claudeOAuthPlanUtilizationAccountKeyForTesting(historyOwnerIdentifier: owner))
        let buckets = try #require(store.planUtilizationHistory[.claude])
        #expect(findSeries(buckets.accounts[key] ?? [], name: .session, windowMinutes: 300)?
            .entries.map(\.usedPercent) == [30, 50])
    }

    @MainActor
    @Test
    func `first sighting without keychain match is quarantined`() async {
        let store = UsageStorePlanUtilizationTests.makeStore()
        let snapshot = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 90,
                windowMinutes: 300,
                resetsAt: nil,
                resetDescription: nil),
            secondary: nil,
            updatedAt: Date())

        await store.recordPlanUtilizationHistorySample(
            provider: .claude,
            snapshot: snapshot,
            claudeOAuthHistoryOwnerIdentifier: String(repeating: "s", count: 64),
            claudeOAuthKeychainCredentialMismatch: true,
            claudeOAuthActiveAccountObservation: .stable(
                identity: UsageStore._activeClaudeAccountIdentityForTesting("uuid-current")),
            isClaudeOAuthSample: true)

        #expect(UsageStore.loadClaudeOAuthAccountUuidMap(from: store.settings.userDefaults).isEmpty)
        #expect(store.planUtilizationHistory[.claude] == nil)
    }

    @MainActor
    @Test
    func `file backed owner records history when keychain comparison is unavailable`() async throws {
        let store = UsageStorePlanUtilizationTests.makeStore()
        let owner = String(repeating: "e", count: 64)
        let key = try #require(
            UsageStore._claudeOAuthPlanUtilizationAccountKeyForTesting(historyOwnerIdentifier: owner))

        await store.recordPlanUtilizationHistorySample(
            provider: .claude,
            snapshot: self.snapshot(usedPercent: 90),
            claudeOAuthHistoryOwnerIdentifier: owner,
            claudeOAuthKeychainCredentialUnavailable: true,
            claudeOAuthActiveAccountObservation: .stable(
                identity: UsageStore._activeClaudeAccountIdentityForTesting("uuid-current")),
            isClaudeOAuthSample: true)

        #expect(UsageStore.loadClaudeOAuthAccountUuidMap(from: store.settings.userDefaults).isEmpty)
        #expect(UsageStore.loadClaudeOAuthAccountBindingCandidateMap(
            from: store.settings.userDefaults).isEmpty)
        let buckets = try #require(store.planUtilizationHistory[.claude])
        #expect(findSeries(buckets.accounts[key] ?? [], name: .session, windowMinutes: 300)?
            .entries.map(\.usedPercent) == [90])
    }

    @MainActor
    @Test
    func `V1 account bindings are discarded so owner mediated history continues`() async throws {
        let store = UsageStorePlanUtilizationTests.makeStore()
        let owner = String(repeating: "a", count: 64)
        let legacyKey = "ClaudeOAuthHistoryOwnerAccountUuidMapV1"
        let legacyMap = [owner: "obsolete-v1-identity"]
        let legacyData = try JSONEncoder().encode(legacyMap)
        store.settings.userDefaults.set(legacyData, forKey: legacyKey)

        await store.recordPlanUtilizationHistorySample(
            provider: .claude,
            snapshot: self.snapshot(usedPercent: 60),
            claudeOAuthHistoryOwnerIdentifier: owner,
            claudeOAuthKeychainCredentialUnavailable: true,
            claudeOAuthActiveAccountObservation: .stable(
                identity: UsageStore._activeClaudeAccountIdentityForTesting("uuid-current")),
            isClaudeOAuthSample: true)

        #expect(store.settings.userDefaults.object(forKey: legacyKey) == nil)
        let key = try #require(
            UsageStore._claudeOAuthPlanUtilizationAccountKeyForTesting(historyOwnerIdentifier: owner))
        let buckets = try #require(store.planUtilizationHistory[.claude])
        #expect(findSeries(buckets.accounts[key] ?? [], name: .session, windowMinutes: 300)?
            .entries.map(\.usedPercent) == [60])
    }

    @MainActor
    @Test
    func `absent keychain still quarantines an owner bound to another account`() async {
        let store = UsageStorePlanUtilizationTests.makeStore()
        let owner = String(repeating: "f", count: 64)
        store.persistClaudeOAuthAccountUuidMap([
            owner: UsageStore._activeClaudeAccountIdentityForTesting("uuid-A"),
        ])

        await store.recordPlanUtilizationHistorySample(
            provider: .claude,
            snapshot: self.snapshot(usedPercent: 90),
            claudeOAuthHistoryOwnerIdentifier: owner,
            claudeOAuthKeychainCredentialAbsent: true,
            claudeOAuthActiveAccountObservation: .stable(
                identity: UsageStore._activeClaudeAccountIdentityForTesting("uuid-B")),
            isClaudeOAuthSample: true)

        #expect(store.planUtilizationHistory[.claude] == nil)
    }

    @MainActor
    @Test
    func `absent keychain records an unbound file owner`() async throws {
        let store = UsageStorePlanUtilizationTests.makeStore()
        let owner = String(repeating: "b", count: 64)
        let key = try #require(
            UsageStore._claudeOAuthPlanUtilizationAccountKeyForTesting(historyOwnerIdentifier: owner))

        await store.recordPlanUtilizationHistorySample(
            provider: .claude,
            snapshot: self.snapshot(usedPercent: 80),
            claudeOAuthHistoryOwnerIdentifier: owner,
            claudeOAuthKeychainCredentialAbsent: true,
            claudeOAuthActiveAccountObservation: .stable(
                identity: UsageStore._activeClaudeAccountIdentityForTesting("uuid-current")),
            isClaudeOAuthSample: true)

        let buckets = try #require(store.planUtilizationHistory[.claude])
        #expect(findSeries(buckets.accounts[key] ?? [], name: .session, windowMinutes: 300)?
            .entries.map(\.usedPercent) == [80])
    }

    @MainActor
    @Test
    func `account change during identity capture cannot bind or write history`() async {
        let store = UsageStorePlanUtilizationTests.makeStore()
        let snapshot = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 90,
                windowMinutes: 300,
                resetsAt: nil,
                resetDescription: nil),
            secondary: nil,
            updatedAt: Date())

        await store.recordPlanUtilizationHistorySample(
            provider: .claude,
            snapshot: snapshot,
            claudeOAuthPersistentRefHash: "account-a-ref",
            claudeOAuthHistoryOwnerIdentifier: String(repeating: "r", count: 64),
            claudeOAuthActiveAccountObservation: .changed,
            isClaudeOAuthSample: true)

        #expect(UsageStore.loadClaudeOAuthAccountUuidMap(from: store.settings.userDefaults).isEmpty)
        #expect(store.planUtilizationHistory[.claude] == nil)
    }

    @MainActor
    @Test
    func `missing active account identity preserves owner scoped history`() async throws {
        let store = UsageStorePlanUtilizationTests.makeStore()
        let owner = String(repeating: "c", count: 64)
        let key = try #require(
            UsageStore._claudeOAuthPlanUtilizationAccountKeyForTesting(historyOwnerIdentifier: owner))
        let snapshot = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 35,
                windowMinutes: 300,
                resetsAt: nil,
                resetDescription: nil),
            secondary: nil,
            updatedAt: Date())

        await UsageStore.withActiveClaudeAccountUuidForTesting(nil) {
            await store.recordPlanUtilizationHistorySample(
                provider: .claude,
                snapshot: snapshot,
                claudeOAuthHistoryOwnerIdentifier: owner,
                claudeOAuthActiveAccountObservation: .stable(identity: nil),
                isClaudeOAuthSample: true)
        }

        let buckets = try #require(store.planUtilizationHistory[.claude])
        #expect(findSeries(buckets.accounts[key] ?? [], name: .session, windowMinutes: 300)?
            .entries.map(\.usedPercent) == [35])
    }

    @MainActor
    @Test
    func `explicit oauth credential ignores Claude Code account identity`() async throws {
        let store = UsageStorePlanUtilizationTests.makeStore()
        let owner = String(repeating: "d", count: 64)
        let key = try #require(
            UsageStore._claudeOAuthPlanUtilizationAccountKeyForTesting(historyOwnerIdentifier: owner))
        let snapshot = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 45,
                windowMinutes: 300,
                resetsAt: nil,
                resetDescription: nil),
            secondary: nil,
            updatedAt: Date())

        await UsageStore.withActiveClaudeAccountUuidForTesting("claude-code-account") {
            await store.recordPlanUtilizationHistorySample(
                provider: .claude,
                snapshot: snapshot,
                claudeOAuthHistoryOwnerIdentifier: owner,
                claudeOAuthActiveAccountObservation: .changed,
                isClaudeOAuthSample: true)
        }

        let buckets = try #require(store.planUtilizationHistory[.claude])
        #expect(findSeries(buckets.accounts[key] ?? [], name: .session, windowMinutes: 300)?
            .entries.map(\.usedPercent) == [45])
        #expect(UsageStore.loadClaudeOAuthAccountUuidMap(from: store.settings.userDefaults).isEmpty)
    }

    @Test
    func `claude oauth history scope requires full auth fingerprint stability`() {
        let stablePersistentRefHash = UsageStore._stableClaudeKeychainPersistentRefHashForTesting(
            beforeFetchFingerprintToken: "stable-fingerprint",
            afterFetchFingerprintToken: "stable-fingerprint",
            beforeFetchPersistentRefHash: "stable-ref",
            afterFetchPersistentRefHash: "stable-ref")
        let changedFingerprintPersistentRefHash = UsageStore._stableClaudeKeychainPersistentRefHashForTesting(
            beforeFetchFingerprintToken: "before-fingerprint",
            afterFetchFingerprintToken: "after-fingerprint",
            beforeFetchPersistentRefHash: "stable-ref",
            afterFetchPersistentRefHash: "stable-ref")

        #expect(stablePersistentRefHash == "stable-ref")
        #expect(changedFingerprintPersistentRefHash == nil)
    }

    @Test
    func `credential change around account read invalidates the observation`() {
        let identityA = UsageStore._activeClaudeAccountIdentityForTesting("uuid-A")
        let identityB = UsageStore._activeClaudeAccountIdentityForTesting("uuid-B")
        let stable = UsageStore._claudeOAuthActiveAccountObservationForTesting(
            identityBeforeFetch: identityB,
            identityAfterFetch: identityB)
        let changed = UsageStore._claudeOAuthActiveAccountObservationForTesting(
            identityBeforeFetch: identityA,
            identityAfterFetch: identityB)
        let unstable = UsageStore._claudeOAuthActiveAccountObservationForTesting(
            identityBeforeFetch: identityB,
            identityAfterFetch: identityB,
            beforeFetchWasStable: false)

        #expect(stable == .stable(identity: identityB))
        #expect(changed == .changed)
        #expect(unstable == .changed)
    }

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