import Foundation
import Testing
@testable import CodexBarCore

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

    @Test
    func `parses O auth credentials`() throws {
        let json = """
        {
          "OPENAI_API_KEY": null,
          "tokens": {
            "access_token": "access-token",
            "refresh_token": "refresh-token",
            "id_token": "id-token",
            "account_id": "account-123"
          },
          "last_refresh": "2025-12-20T12:34:56Z"
        }
        """
        let creds = try CodexOAuthCredentialsStore.parse(data: Data(json.utf8))
        #expect(creds.accessToken == "access-token")
        #expect(creds.refreshToken == "refresh-token")
        #expect(creds.idToken == "id-token")
        #expect(creds.accountId == "account-123")
        #expect(creds.lastRefresh != nil)
    }

    @Test
    func `parses legacy camel case O auth credentials`() throws {
        let json = """
        {
          "OPENAI_API_KEY": null,
          "tokens": {
            "accessToken": "access-token",
            "refreshToken": "refresh-token",
            "idToken": "id-token",
            "accountId": "account-123"
          },
          "last_refresh": "2025-12-20T12:34:56Z"
        }
        """
        let creds = try CodexOAuthCredentialsStore.parse(data: Data(json.utf8))
        #expect(creds.accessToken == "access-token")
        #expect(creds.refreshToken == "refresh-token")
        #expect(creds.idToken == "id-token")
        #expect(creds.accountId == "account-123")
        #expect(creds.lastRefresh != nil)
    }

    @Test
    func `parses API key credentials`() throws {
        let json = """
        {
          "OPENAI_API_KEY": "sk-test"
        }
        """
        let creds = try CodexOAuthCredentialsStore.parse(data: Data(json.utf8))
        #expect(creds.accessToken == "sk-test")
        #expect(creds.refreshToken.isEmpty)
        #expect(creds.idToken == nil)
        #expect(creds.accountId == nil)
    }

    @Test
    func `reset-credit token load ignores an API key beside O auth tokens`() throws {
        let home = FileManager.default.temporaryDirectory
            .appendingPathComponent("codexbar-reset-credit-oauth-\(UUID().uuidString)")
        try FileManager.default.createDirectory(at: home, withIntermediateDirectories: true)
        defer { try? FileManager.default.removeItem(at: home) }
        let json = """
        {
          "OPENAI_API_KEY": "sk-test",
          "tokens": {
            "access_token": "oauth-access-token",
            "refresh_token": "oauth-refresh-token",
            "account_id": "account-123"
          },
          "last_refresh": "2026-07-01T12:00:00Z"
        }
        """
        try Data(json.utf8).write(to: home.appendingPathComponent("auth.json"))

        let credentials = try CodexOAuthCredentialsStore.loadOAuthTokens(env: ["CODEX_HOME": home.path])

        #expect(credentials.accessToken == "oauth-access-token")
        #expect(credentials.refreshToken == "oauth-refresh-token")
        #expect(credentials.accountId == "account-123")
    }

    @Test
    func `decodes credits balance string`() throws {
        let json = """
        {
          "plan_type": "pro",
          "rate_limit": {
            "primary_window": {
              "used_percent": 12,
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            }
          },
          "credits": {
            "has_credits": false,
            "unlimited": false,
            "balance": "0"
          }
        }
        """
        let response = try CodexOAuthUsageFetcher._decodeUsageResponseForTesting(Data(json.utf8))
        #expect(response.planType?.rawValue == "pro")
        #expect(response.credits?.balance == 0)
        #expect(response.credits?.hasCredits == false)
        #expect(response.credits?.unlimited == false)
    }

    @Test
    func `decodes prolite plan type without failing usage mapping`() throws {
        let json = """
        {
          "plan_type": "prolite",
          "rate_limit": {
            "primary_window": {
              "used_percent": 12,
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            }
          }
        }
        """
        let response = try CodexOAuthUsageFetcher._decodeUsageResponseForTesting(Data(json.utf8))
        #expect(response.planType?.rawValue == "prolite")

        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
        #expect(mapped?.primary?.usedPercent == 12)
    }

    @Test
    func `maps usage windows from O auth`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": 22,
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            },
            "secondary_window": {
              "used_percent": 43,
              "reset_at": 1767407914,
              "limit_window_seconds": 604800
            }
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
        let snapshot = try #require(mapped)
        #expect(snapshot.primary?.usedPercent == 22)
        #expect(snapshot.primary?.windowMinutes == 300)
        #expect(snapshot.secondary?.usedPercent == 43)
        #expect(snapshot.secondary?.windowMinutes == 10080)
        #expect(snapshot.primary?.resetsAt != nil)
        #expect(snapshot.secondary?.resetsAt != nil)
    }

    @Test
    func `O auth response with precise windows maps to exact confidence`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": 22,
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            },
            "secondary_window": {
              "used_percent": 43,
              "reset_at": 1767407914,
              "limit_window_seconds": 604800
            }
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let result = try CodexOAuthFetchStrategy._mapResultForTesting(Data(json.utf8), credentials: creds)

        #expect(result.sourceLabel == "oauth")
        #expect(result.usage.dataConfidence == .exact)
        #expect(result.usage.primary?.usedPercent == 22)
        #expect(result.usage.secondary?.usedPercent == 43)
    }

    @Test
    func `O auth response with malformed additional window maps to unknown confidence`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": 22,
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            }
          },
          "additional_rate_limits": [
            {
              "limit_name": "GPT-5.3-Codex-Spark",
              "metered_feature": "gpt_5_3_codex_spark",
              "rate_limit": {
                "primary_window": { "used_percent": "bad" }
              }
            }
          ]
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let result = try CodexOAuthFetchStrategy._mapResultForTesting(Data(json.utf8), credentials: creds)

        #expect(result.usage.primary?.usedPercent == 22)
        #expect(result.usage.extraRateWindows == nil)
        #expect(result.usage.dataConfidence == .unknown)
    }

    @Test
    func `maps free weekly only window into secondary`() throws {
        let json = """
        {
          "plan_type": "free",
          "rate_limit": {
            "primary_window": {
              "used_percent": 0,
              "reset_at": 1775468693,
              "limit_window_seconds": 604800
            },
            "secondary_window": null
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
        let snapshot = try #require(mapped)
        #expect(snapshot.primary == nil)
        #expect(snapshot.secondary?.usedPercent == 0)
        #expect(snapshot.secondary?.windowMinutes == 10080)
    }

    @Test
    func `keeps single session window as primary`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": 9,
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            },
            "secondary_window": null
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
        let snapshot = try #require(mapped)
        #expect(snapshot.primary?.usedPercent == 9)
        #expect(snapshot.primary?.windowMinutes == 300)
        #expect(snapshot.secondary == nil)
    }

    @Test
    func `preserves unknown single window as primary`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": 17,
              "reset_at": 1766948068,
              "limit_window_seconds": 32400
            },
            "secondary_window": null
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
        let snapshot = try #require(mapped)
        #expect(snapshot.primary?.usedPercent == 17)
        #expect(snapshot.primary?.windowMinutes == 540)
        #expect(snapshot.secondary == nil)
    }

    @Test
    func `preserves unknown secondary only window as primary`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": null,
            "secondary_window": {
              "used_percent": 17,
              "reset_at": 1766948068,
              "limit_window_seconds": 32400
            }
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
        let snapshot = try #require(mapped)
        #expect(snapshot.primary?.usedPercent == 17)
        #expect(snapshot.primary?.windowMinutes == 540)
        #expect(snapshot.secondary == nil)
    }

    @Test
    func `swaps reversed weekly and unknown windows`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": 43,
              "reset_at": 1767407914,
              "limit_window_seconds": 604800
            },
            "secondary_window": {
              "used_percent": 17,
              "reset_at": 1766948068,
              "limit_window_seconds": 32400
            }
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
        let snapshot = try #require(mapped)
        #expect(snapshot.primary?.usedPercent == 17)
        #expect(snapshot.primary?.windowMinutes == 540)
        #expect(snapshot.secondary?.usedPercent == 43)
        #expect(snapshot.secondary?.windowMinutes == 10080)
    }

    @Test
    func `returns nil when O auth usage has no windows`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": null,
            "secondary_window": null
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let snapshot = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
        #expect(snapshot == nil)
    }

    @Test
    func `keeps valid window when secondary window is malformed`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": 18,
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            },
            "secondary_window": {
              "used_percent": "bad",
              "reset_at": 1767407914,
              "limit_window_seconds": 604800
            }
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let snapshot = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
        #expect(snapshot?.primary?.usedPercent == 18)
        #expect(snapshot?.secondary == nil)

        let result = try CodexOAuthFetchStrategy._mapResultForTesting(Data(json.utf8), credentials: creds)
        #expect(result.usage.primary?.usedPercent == 18)
        #expect(result.usage.secondary == nil)
        #expect(result.usage.dataConfidence == .unknown)
    }

    @Test
    func `auto mode keeps weekly window when primary window is malformed`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": "bad",
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            },
            "secondary_window": {
              "used_percent": 43,
              "reset_at": 1767407914,
              "limit_window_seconds": 604800
            }
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())

        let result = try CodexOAuthFetchStrategy._mapResultForTesting(
            Data(json.utf8),
            credentials: creds,
            sourceMode: .auto)

        #expect(result.usage.primary == nil)
        #expect(result.usage.secondary?.usedPercent == 43)
        #expect(result.usage.secondary?.windowMinutes == 10080)
    }

    @Test
    func `explicit oauth keeps weekly window when primary window is malformed`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": "bad",
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            },
            "secondary_window": {
              "used_percent": 43,
              "reset_at": 1767407914,
              "limit_window_seconds": 604800
            }
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())

        let result = try CodexOAuthFetchStrategy._mapResultForTesting(
            Data(json.utf8),
            credentials: creds,
            sourceMode: .oauth)

        #expect(result.usage.primary == nil)
        #expect(result.usage.secondary?.usedPercent == 43)
        #expect(result.usage.secondary?.windowMinutes == 10080)
    }

    @Test
    func `auto mode preserves reversed session window when primary window is malformed`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": "bad",
              "reset_at": 1767407914,
              "limit_window_seconds": 604800
            },
            "secondary_window": {
              "used_percent": 18,
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            }
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())

        let result = try CodexOAuthFetchStrategy._mapResultForTesting(
            Data(json.utf8),
            credentials: creds,
            sourceMode: .auto)

        #expect(result.usage.primary?.usedPercent == 18)
        #expect(result.usage.primary?.windowMinutes == 300)
        #expect(result.usage.secondary == nil)
    }

    @Test
    func `auto mode keeps weekly window when reversed session window is malformed`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": 43,
              "reset_at": 1767407914,
              "limit_window_seconds": 604800
            },
            "secondary_window": {
              "used_percent": "bad",
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            }
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())

        let result = try CodexOAuthFetchStrategy._mapResultForTesting(
            Data(json.utf8),
            credentials: creds,
            sourceMode: .auto)

        #expect(result.usage.primary == nil)
        #expect(result.usage.secondary?.usedPercent == 43)
        #expect(result.usage.secondary?.windowMinutes == 10080)
    }

    @Test
    func `explicit oauth keeps weekly window when reversed session window is malformed`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": 43,
              "reset_at": 1767407914,
              "limit_window_seconds": 604800
            },
            "secondary_window": {
              "used_percent": "bad",
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            }
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())

        let result = try CodexOAuthFetchStrategy._mapResultForTesting(
            Data(json.utf8),
            credentials: creds,
            sourceMode: .oauth)

        #expect(result.usage.primary == nil)
        #expect(result.usage.secondary?.usedPercent == 43)
        #expect(result.usage.secondary?.windowMinutes == 10080)
    }

    @Test
    func `ignores malformed credits payload while keeping usage`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": {
              "used_percent": 22,
              "reset_at": 1766948068,
              "limit_window_seconds": 18000
            }
          },
          "credits": {
            "has_credits": false,
            "unlimited": false,
            "balance": []
          }
        }
        """
        let response = try CodexOAuthUsageFetcher._decodeUsageResponseForTesting(Data(json.utf8))
        #expect(response.credits?.hasCredits == false)
        #expect(response.credits?.unlimited == false)
        #expect(response.credits?.balance == nil)

        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())
        let snapshot = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
        #expect(snapshot?.primary?.usedPercent == 22)
    }

    @Test
    func `credits only O auth payload still returns credits result`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": null,
            "secondary_window": null
          },
          "credits": {
            "has_credits": true,
            "unlimited": false,
            "balance": "14.5"
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())

        let result = try CodexOAuthFetchStrategy._mapResultForTesting(Data(json.utf8), credentials: creds)

        #expect(result.usage.primary == nil)
        #expect(result.usage.secondary == nil)
        #expect(result.credits?.remaining == 14.5)
        #expect(result.sourceLabel == "oauth")
    }

    @Test
    func `credits only O auth payload returns credits in auto mode`() throws {
        let json = """
        {
          "rate_limit": {
            "primary_window": null,
            "secondary_window": null
          },
          "credits": {
            "has_credits": true,
            "unlimited": false,
            "balance": "14.5"
          }
        }
        """
        let creds = CodexOAuthCredentials(
            accessToken: "access",
            refreshToken: "refresh",
            idToken: nil,
            accountId: nil,
            lastRefresh: Date())

        let result = try CodexOAuthFetchStrategy._mapResultForTesting(
            Data(json.utf8),
            credentials: creds,
            sourceMode: .auto)

        #expect(result.usage.primary == nil)
        #expect(result.usage.secondary == nil)
        #expect(result.credits?.remaining == 14.5)
        #expect(result.sourceLabel == "oauth")
    }

    @Test
    func `auto mode only falls back from O auth on auth failures`() {
        let strategy = CodexOAuthFetchStrategy()
        let context = self.makeContext(sourceMode: .auto)

        #expect(strategy.shouldFallback(on: CodexOAuthFetchError.unauthorized, context: context))
        #expect(strategy.shouldFallback(on: CodexOAuthCredentialsError.notFound, context: context))
        #expect(strategy.shouldFallback(on: CodexOAuthCredentialsError.missingTokens, context: context))
        #expect(strategy.shouldFallback(on: CodexTokenRefresher.RefreshError.expired, context: context))
        #expect(strategy.shouldFallback(on: CodexTokenRefresher.RefreshError.revoked, context: context))
        #expect(strategy.shouldFallback(on: CodexTokenRefresher.RefreshError.reused, context: context))

        #expect(!strategy.shouldFallback(on: UsageError.noRateLimitsFound, context: context))
        #expect(!strategy.shouldFallback(on: CodexOAuthCredentialsError.decodeFailed("bad json"), context: context))
        #expect(!strategy.shouldFallback(on: CodexOAuthFetchError.invalidResponse, context: context))
        #expect(!strategy.shouldFallback(on: CodexOAuthFetchError.serverError(500, "offline"), context: context))
        #expect(!strategy.shouldFallback(
            on: CodexOAuthFetchError.networkError(URLError(.notConnectedToInternet)),
            context: context))
        #expect(!strategy.shouldFallback(
            on: CodexTokenRefresher.RefreshError.networkError(URLError(.timedOut)),
            context: context))
    }

    @Test
    func `non 401 invalid grant refresh failure is treated as revoked`() {
        let data = Data(#"{"error":"invalid_grant"}"#.utf8)
        let error = CodexTokenRefresher._refreshFailureErrorForTesting(statusCode: 400, data: data)

        switch error {
        case .revoked:
            break
        default:
            Issue.record("Expected invalid_grant to be treated as revoked")
        }
    }

    @Test
    func `non auth refresh failure remains invalid response`() {
        let data = Data(#"{"error":"invalid_request"}"#.utf8)
        let error = CodexTokenRefresher._refreshFailureErrorForTesting(statusCode: 400, data: data)

        switch error {
        case let .invalidResponse(message):
            #expect(message == "Status 400")
        default:
            Issue.record("Expected invalid_request to remain an invalid response")
        }
    }

    @Test
    func `explicit O auth mode never falls back to CLI`() {
        let strategy = CodexOAuthFetchStrategy()
        let context = self.makeContext(sourceMode: .oauth)

        #expect(!strategy.shouldFallback(on: CodexOAuthFetchError.unauthorized, context: context))
        #expect(!strategy.shouldFallback(on: CodexTokenRefresher.RefreshError.expired, context: context))
    }

    @Test
    func `resolves chat GPT usage URL from config`() {
        let config = "chatgpt_base_url = \"https://chatgpt.com/backend-api/\"\n"
        let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config)
        #expect(url.absoluteString == "https://chatgpt.com/backend-api/wham/usage")
    }

    @Test
    func `resolves codex usage URL from config`() {
        let config = "chatgpt_base_url = \"https://api.openai.com\"\n"
        let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config)
        #expect(url.absoluteString == "https://api.openai.com/api/codex/usage")
    }

    @Test
    func `normalizes chat GPT base URL without backend API`() {
        let config = "chatgpt_base_url = \"https://chat.openai.com\"\n"
        let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config)
        #expect(url.absoluteString == "https://chat.openai.com/backend-api/wham/usage")
    }
}
