import Testing
@testable import CodexBarCLI
@testable import CodexBarCore

struct CLIWebFallbackTests {
    private func makeContext(
        runtime: ProviderRuntime = .cli,
        sourceMode: ProviderSourceMode = .auto,
        settings: ProviderSettingsSnapshot? = nil) -> ProviderFetchContext
    {
        let browserDetection = BrowserDetection(cacheTTL: 0)
        return ProviderFetchContext(
            runtime: runtime,
            sourceMode: sourceMode,
            includeCredits: true,
            webTimeout: 60,
            webDebugDumpHTML: false,
            verbose: false,
            env: [:],
            settings: settings,
            fetcher: UsageFetcher(),
            claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection),
            browserDetection: browserDetection)
    }

    private func makeClaudeSettingsSnapshot(cookieHeader: String?) -> ProviderSettingsSnapshot {
        ProviderSettingsSnapshot.make(
            claude: .init(
                usageDataSource: .auto,
                webExtrasEnabled: false,
                cookieSource: .manual,
                manualCookieHeader: cookieHeader))
    }

    @Test
    func `codex falls back when cookies missing`() {
        let context = self.makeContext()
        let strategy = CodexWebDashboardStrategy()
        #expect(strategy.shouldFallback(
            on: OpenAIDashboardBrowserCookieImporter.ImportError.noCookiesFound,
            context: context))
        #expect(strategy.shouldFallback(
            on: OpenAIDashboardBrowserCookieImporter.ImportError.noMatchingAccount(found: []),
            context: context))
        #expect(strategy.shouldFallback(
            on: OpenAIDashboardBrowserCookieImporter.ImportError.browserAccessDenied(details: "no access"),
            context: context))
        #expect(strategy.shouldFallback(
            on: OpenAIDashboardBrowserCookieImporter.ImportError.dashboardStillRequiresLogin,
            context: context))
        #expect(strategy.shouldFallback(
            on: OpenAIDashboardFetcher.FetchError.loginRequired,
            context: context))
    }

    @Test
    func `codex falls back for dashboard data errors in auto`() {
        let context = self.makeContext()
        let strategy = CodexWebDashboardStrategy()
        #expect(strategy.shouldFallback(
            on: OpenAIDashboardFetcher.FetchError.noDashboardData(body: "missing"),
            context: context))
    }

    @Test
    func `codex retries fresh browser import for missing usage and no data`() {
        #expect(CodexWebDashboardStrategy.shouldRetryWithFreshBrowserImport(
            after: OpenAIWebCodexError.missingUsage))
        #expect(CodexWebDashboardStrategy.shouldRetryWithFreshBrowserImport(
            after: OpenAIDashboardFetcher.FetchError.noDashboardData(body: "missing")))
        #expect(!CodexWebDashboardStrategy.shouldRetryWithFreshBrowserImport(
            after: OpenAIDashboardFetcher.FetchError.loginRequired))
        #expect(!CodexWebDashboardStrategy.shouldRetryWithFreshBrowserImport(
            after: OpenAIWebCodexError.timedOut(seconds: 30)))
    }

    @Test
    func `codex shared deadline timeout has useful error`() {
        let error = OpenAIWebCodexError.timedOut(seconds: 30)
        #expect(error.localizedDescription == "OpenAI web dashboard fetch timed out after 30 seconds.")
    }

    @Test
    func `codex display only falls back in auto`() {
        let strategy = CodexWebDashboardStrategy()
        let decision = self.makeCodexDisplayOnlyDecision()

        #expect(strategy.shouldFallback(
            on: CodexDashboardPolicyError.displayOnly(decision),
            context: self.makeContext(sourceMode: .auto)))
    }

    @Test
    func `codex display only does not fall back in explicit web`() {
        let strategy = CodexWebDashboardStrategy()
        let decision = self.makeCodexDisplayOnlyDecision()

        #expect(!strategy.shouldFallback(
            on: CodexDashboardPolicyError.displayOnly(decision),
            context: self.makeContext(sourceMode: .web)))
    }

    @Test
    func `codex web strategy is unavailable when managed account store is unreadable`() async {
        let context = self.makeContext(settings: ProviderSettingsSnapshot.make(
            codex: .init(
                usageDataSource: .auto,
                cookieSource: .auto,
                manualCookieHeader: nil,
                managedAccountStoreUnreadable: true)))
        let strategy = CodexWebDashboardStrategy()
        let available = await strategy.isAvailable(context)

        #expect(!available)
    }

    @Test
    func `codex web strategy is unavailable when selected managed target is unavailable`() async {
        let context = self.makeContext(settings: ProviderSettingsSnapshot.make(
            codex: .init(
                usageDataSource: .auto,
                cookieSource: .auto,
                manualCookieHeader: nil,
                managedAccountTargetUnavailable: true)))
        let strategy = CodexWebDashboardStrategy()
        let available = await strategy.isAvailable(context)

        #expect(!available)
    }

    @Test
    func `codex web strategy fails closed when profile target is unavailable`() async {
        let settings = ProviderSettingsSnapshot.make(
            codex: .init(
                usageDataSource: .auto,
                cookieSource: .auto,
                manualCookieHeader: nil,
                profileAccountTargetUnavailable: true))
        let strategy = CodexWebDashboardStrategy()

        let autoContext = self.makeContext(sourceMode: .auto, settings: settings)
        let autoAvailable = await strategy.isAvailable(autoContext)
        #expect(!autoAvailable)

        let explicitWebContext = self.makeContext(sourceMode: .web, settings: settings)
        let explicitWebAvailable = await strategy.isAvailable(explicitWebContext)
        #expect(explicitWebAvailable)
        do {
            _ = try await strategy.fetch(explicitWebContext)
            Issue.record("Expected unavailable profile target to require login")
        } catch OpenAIDashboardFetcher.FetchError.loginRequired {
            // Expected before browser import can accept an arbitrary account.
        } catch {
            Issue.record("Unexpected error: \(error)")
        }
    }

    @Test
    func `claude falls back when no session key`() {
        let context = self.makeContext()
        let strategy = ClaudeWebFetchStrategy(browserDetection: BrowserDetection(cacheTTL: 0))
        #expect(strategy.shouldFallback(on: ClaudeWebAPIFetcher.FetchError.noSessionKeyFound, context: context))
        #expect(strategy.shouldFallback(on: ClaudeWebAPIFetcher.FetchError.unauthorized, context: context))
    }

    @Test
    func `claude CLI fallback is enabled only for app auto`() {
        let webAvailableStrategy = ClaudeCLIFetchStrategy(
            useWebExtras: false,
            includePrepaidBalance: false,
            manualCookieHeader: nil,
            browserDetection: BrowserDetection(cacheTTL: 0),
            hasWebFallback: true)
        let webUnavailableStrategy = ClaudeCLIFetchStrategy(
            useWebExtras: false,
            includePrepaidBalance: false,
            manualCookieHeader: nil,
            browserDetection: BrowserDetection(cacheTTL: 0),
            hasWebFallback: false)
        let error = ClaudeUsageError.parseFailed("cli failed")
        let webAvailableSettings = self.makeClaudeSettingsSnapshot(cookieHeader: "sessionKey=sk-ant-test")
        let webUnavailableSettings = self.makeClaudeSettingsSnapshot(cookieHeader: "foo=bar")

        #expect(webAvailableStrategy.shouldFallback(
            on: error,
            context: self.makeContext(runtime: .app, sourceMode: .auto, settings: webAvailableSettings)))
        #expect(!webUnavailableStrategy.shouldFallback(
            on: error,
            context: self.makeContext(runtime: .app, sourceMode: .auto, settings: webUnavailableSettings)))
        #expect(!webAvailableStrategy.shouldFallback(
            on: error,
            context: self.makeContext(runtime: .app, sourceMode: .cli)))
        #expect(!webAvailableStrategy.shouldFallback(
            on: error,
            context: self.makeContext(runtime: .app, sourceMode: .web)))
        #expect(!webAvailableStrategy.shouldFallback(
            on: error,
            context: self.makeContext(runtime: .app, sourceMode: .oauth)))
        #expect(!webAvailableStrategy.shouldFallback(
            on: error,
            context: self.makeContext(runtime: .cli, sourceMode: .auto)))
    }

    @Test
    func `claude web fallback is disabled for app auto`() {
        let strategy = ClaudeWebFetchStrategy(browserDetection: BrowserDetection(cacheTTL: 0))
        let error = ClaudeWebAPIFetcher.FetchError.unauthorized
        #expect(strategy.shouldFallback(on: error, context: self.makeContext(runtime: .cli, sourceMode: .auto)))
        #expect(!strategy.shouldFallback(on: error, context: self.makeContext(runtime: .app, sourceMode: .auto)))
    }

    private func makeCodexDisplayOnlyDecision() -> CodexDashboardAuthorityDecision {
        CodexDashboardAuthority.evaluate(
            CodexDashboardAuthorityInput(
                sourceKind: .liveWeb,
                proof: CodexDashboardOwnershipProofContext(
                    currentIdentity: .emailOnly(normalizedEmail: "shared@example.com"),
                    expectedScopedEmail: nil,
                    trustedCurrentUsageEmail: nil,
                    dashboardSignedInEmail: "shared@example.com",
                    knownOwners: [
                        CodexDashboardKnownOwnerCandidate(
                            identity: .providerAccount(id: "acct-alpha"),
                            normalizedEmail: "shared@example.com"),
                        CodexDashboardKnownOwnerCandidate(
                            identity: .providerAccount(id: "acct-beta"),
                            normalizedEmail: "shared@example.com"),
                    ]),
                routing: CodexDashboardRoutingHints(
                    targetEmail: "shared@example.com",
                    lastKnownDashboardRoutingEmail: nil)))
    }
}
