import Foundation
import Testing
@testable import CodexBar
@testable import CodexBarCore

struct ClaudeWebRefreshResilienceTests {
    @Test
    func `web unauthorized respects failure gate while keeping prior Claude snapshot`() async throws {
        try await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {
            let tempDir = FileManager.default.temporaryDirectory
                .appendingPathComponent(UUID().uuidString, isDirectory: true)
            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
            let fileURL = tempDir.appendingPathComponent("missing-credentials.json")

            try await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {
                let prior = Self.makePriorSnapshot()
                let store = try await MainActor.run {
                    try Self.makeStore(
                        suite: "ClaudeWebRefreshResilienceTests-web-unauthorized",
                        prior: prior)
                }

                await store.refreshProvider(.claude)
                let firstResult = await MainActor.run {
                    (
                        updatedAt: store.snapshot(for: .claude)?.updatedAt,
                        hasError: store.error(for: .claude) != nil)
                }

                #expect(firstResult.updatedAt == prior.updatedAt)
                #expect(!firstResult.hasError)

                await store.refreshProvider(.claude)
                let secondResult = await MainActor.run {
                    (
                        updatedAt: store.snapshot(for: .claude)?.updatedAt,
                        error: store.error(for: .claude))
                }

                #expect(secondResult.updatedAt == prior.updatedAt)
                #expect(secondResult.error == ClaudeWebAPIFetcher.FetchError.unauthorized.localizedDescription)
            }
        }
    }

    @Test
    func `web unauthorized without prior Claude snapshot still surfaces failure`() async throws {
        try await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {
            let tempDir = FileManager.default.temporaryDirectory
                .appendingPathComponent(UUID().uuidString, isDirectory: true)
            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
            let fileURL = tempDir.appendingPathComponent("missing-credentials.json")

            try await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {
                let store = try await MainActor.run {
                    try Self.makeStore(
                        suite: "ClaudeWebRefreshResilienceTests-web-unauthorized-no-prior",
                        prior: nil)
                }

                await store.refreshProvider(.claude)
                let result = await MainActor.run {
                    (
                        hasSnapshot: store.snapshot(for: .claude) != nil,
                        error: store.error(for: .claude))
                }

                #expect(!result.hasSnapshot)
                #expect(result.error == ClaudeWebAPIFetcher.FetchError.unauthorized.localizedDescription)
            }
        }
    }

    @Test
    func `web parse failure clears prior Claude snapshot when surfaced`() async throws {
        try await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {
            let tempDir = FileManager.default.temporaryDirectory
                .appendingPathComponent(UUID().uuidString, isDirectory: true)
            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
            let fileURL = tempDir.appendingPathComponent("missing-credentials.json")

            try await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {
                let prior = Self.makePriorSnapshot()
                let store = try await MainActor.run {
                    try Self.makeStore(
                        suite: "ClaudeWebRefreshResilienceTests-web-parse",
                        prior: prior,
                        strategy: ClaudeWebParseFailureFetchStrategy(message: "Missing Current session."))
                }

                await store.refreshProvider(.claude)
                let firstResult = await MainActor.run {
                    (
                        updatedAt: store.snapshot(for: .claude)?.updatedAt,
                        hasError: store.error(for: .claude) != nil)
                }

                #expect(firstResult.updatedAt == prior.updatedAt)
                #expect(!firstResult.hasError)

                await store.refreshProvider(.claude)
                let secondResult = await MainActor.run {
                    (
                        hasSnapshot: store.snapshot(for: .claude) != nil,
                        error: store.error(for: .claude))
                }

                #expect(!secondResult.hasSnapshot)
                #expect(secondResult.error?.localizedCaseInsensitiveContains("Missing Current session") == true)
            }
        }
    }

    @MainActor
    private static func makeStore(
        suite: String,
        prior: UsageSnapshot?,
        strategy: any ProviderFetchStrategy = ClaudeWebUnauthorizedFetchStrategy()) throws -> UsageStore
    {
        let settings = self.makeSettingsStore(suite: suite)
        settings.refreshFrequency = .manual
        settings.statusChecksEnabled = false
        settings.claudeUsageDataSource = .web

        let metadata = ProviderRegistry.shared.metadata
        for provider in UsageProvider.allCases {
            try settings.setProviderEnabled(
                provider: provider,
                metadata: #require(metadata[provider]),
                enabled: provider == .claude)
        }

        let store = UsageStore(
            fetcher: UsageFetcher(environment: [:]),
            browserDetection: BrowserDetection(cacheTTL: 0),
            settings: settings,
            startupBehavior: .testing,
            environmentBase: [:])
        if let prior {
            store._setSnapshotForTesting(prior, provider: .claude)
        }

        let baseSpec = try #require(store.providerSpecs[.claude])
        let descriptor = ProviderDescriptor(
            id: .claude,
            metadata: baseSpec.descriptor.metadata,
            branding: baseSpec.descriptor.branding,
            tokenCost: baseSpec.descriptor.tokenCost,
            fetchPlan: ProviderFetchPlan(
                sourceModes: [.web],
                pipeline: ProviderFetchPipeline { _ in [strategy] }),
            cli: baseSpec.descriptor.cli)
        store.providerSpecs[.claude] = ProviderSpec(
            style: baseSpec.style,
            isEnabled: baseSpec.isEnabled,
            descriptor: descriptor,
            makeFetchContext: baseSpec.makeFetchContext)
        return store
    }

    private static func makePriorSnapshot() -> UsageSnapshot {
        UsageSnapshot(
            primary: RateWindow(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
            secondary: RateWindow(
                usedPercent: 34,
                windowMinutes: 10080,
                resetsAt: nil,
                resetDescription: nil),
            updatedAt: Date(timeIntervalSince1970: 1_800_000_000),
            identity: ProviderIdentitySnapshot(
                providerID: .claude,
                accountEmail: "claude@example.com",
                accountOrganization: nil,
                loginMethod: "Pro"))
    }

    @MainActor
    private static func makeSettingsStore(suite: String) -> SettingsStore {
        let defaults = UserDefaults(suiteName: suite)!
        defaults.removePersistentDomain(forName: suite)
        let settings = SettingsStore(
            userDefaults: defaults,
            configStore: testConfigStore(suiteName: suite),
            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())
        settings.providerDetectionCompleted = true
        return settings
    }
}

private struct ClaudeWebUnauthorizedFetchStrategy: ProviderFetchStrategy {
    let id = "test.claude-web-unauthorized"
    let kind: ProviderFetchKind = .web

    func isAvailable(_: ProviderFetchContext) async -> Bool {
        true
    }

    func fetch(_: ProviderFetchContext) async throws -> ProviderFetchResult {
        throw ClaudeWebAPIFetcher.FetchError.unauthorized
    }

    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
        false
    }
}

private struct ClaudeWebParseFailureFetchStrategy: ProviderFetchStrategy {
    let id = "test.claude-web-parse-failure"
    let kind: ProviderFetchKind = .web
    let message: String

    func isAvailable(_: ProviderFetchContext) async -> Bool {
        true
    }

    func fetch(_: ProviderFetchContext) async throws -> ProviderFetchResult {
        throw ClaudeStatusProbeError.parseFailed(self.message)
    }

    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
        false
    }
}
