import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

public enum CodexTokenRefresher {
    private static let refreshEndpoint = URL(string: "https://auth.openai.com/oauth/token")!
    private static let clientID = "app_EMoamEEZ73f0CkXaXp7hrann"

    public enum RefreshError: LocalizedError, Sendable {
        case expired
        case revoked
        case reused
        case networkError(Error)
        case invalidResponse(String)

        public var errorDescription: String? {
            switch self {
            case .expired:
                "Refresh token expired. Please run `codex` to log in again."
            case .revoked:
                "Refresh token was revoked. Please run `codex` to log in again."
            case .reused:
                "Refresh token was already used. Please run `codex` to log in again."
            case let .networkError(error):
                "Network error during token refresh: \(error.localizedDescription)"
            case let .invalidResponse(message):
                "Invalid refresh response: \(message)"
            }
        }
    }

    public static func refresh(_ credentials: CodexOAuthCredentials) async throws -> CodexOAuthCredentials {
        try await self.refresh(credentials, session: CodexAuthenticatedHTTPTransport.current)
    }

    static func refresh(
        _ credentials: CodexOAuthCredentials,
        session transport: any ProviderHTTPTransport) async throws -> CodexOAuthCredentials
    {
        guard !credentials.refreshToken.isEmpty else {
            return credentials
        }

        var request = URLRequest(
            url: Self.refreshEndpoint,
            cachePolicy: .reloadIgnoringLocalCacheData,
            timeoutInterval: 30)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        let body: [String: String] = [
            "client_id": Self.clientID,
            "grant_type": "refresh_token",
            "refresh_token": credentials.refreshToken,
            "scope": "openid profile email",
        ]
        request.httpBody = try JSONSerialization.data(withJSONObject: body)

        do {
            let response = try await transport.response(for: request)
            let data = response.data
            guard response.statusCode == 200 else {
                throw Self.refreshFailureError(statusCode: response.statusCode, data: data)
            }

            guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
                throw RefreshError.invalidResponse("Invalid JSON")
            }

            let newAccessToken = json["access_token"] as? String ?? credentials.accessToken
            let newRefreshToken = json["refresh_token"] as? String ?? credentials.refreshToken
            let newIdToken = json["id_token"] as? String ?? credentials.idToken

            return CodexOAuthCredentials(
                accessToken: newAccessToken,
                refreshToken: newRefreshToken,
                idToken: newIdToken,
                accountId: credentials.accountId,
                lastRefresh: Date())
        } catch let error as RefreshError {
            throw error
        } catch {
            throw RefreshError.networkError(error)
        }
    }

    private static func extractErrorCode(from data: Data) -> String? {
        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil }
        if let error = json["error"] as? [String: Any], let code = error["code"] as? String { return code }
        if let error = json["error"] as? String { return error }
        return json["code"] as? String
    }

    private static func refreshFailureError(statusCode: Int, data: Data) -> RefreshError {
        if let errorCode = extractErrorCode(from: data) {
            switch errorCode.lowercased() {
            case "refresh_token_expired":
                return .expired
            case "refresh_token_reused":
                return .reused
            case "invalid_grant", "refresh_token_invalidated":
                return .revoked
            default:
                break
            }
        }

        if statusCode == 401 {
            return .expired
        }

        return .invalidResponse("Status \(statusCode)")
    }
}

#if DEBUG
extension CodexTokenRefresher {
    static func _refreshFailureErrorForTesting(statusCode: Int, data: Data) -> RefreshError {
        self.refreshFailureError(statusCode: statusCode, data: data)
    }
}
#endif
