import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#endif

public struct GeminiModelQuota: Sendable {
    public let modelId: String
    public let percentLeft: Double
    public let resetTime: Date?
    public let resetDescription: String?
}

public struct GeminiStatusSnapshot: Sendable {
    public let modelQuotas: [GeminiModelQuota]
    public let rawText: String
    public let accountEmail: String?
    public let accountPlan: String?

    // Convenience: lowest quota across all models (for icon display)
    public var lowestPercentLeft: Double? {
        self.modelQuotas.min(by: { $0.percentLeft < $1.percentLeft })?.percentLeft
    }

    /// Legacy compatibility
    public var dailyPercentLeft: Double? {
        self.lowestPercentLeft
    }

    public var resetDescription: String? {
        self.modelQuotas.min(by: { $0.percentLeft < $1.percentLeft })?.resetDescription
    }

    /// Converts Gemini quotas to a unified UsageSnapshot.
    /// Groups quotas by tier: Pro (24h window) as primary, Flash (24h window) as secondary,
    /// Flash Lite (24h window) as tertiary.
    public func toUsageSnapshot() -> UsageSnapshot {
        let lower = self.modelQuotas.map { ($0.modelId.lowercased(), $0) }
        let flashLiteQuotas = lower.filter { Self.isFlashLiteModel(id: $0.0) }.map(\.1)
        let flashQuotas = lower.filter { Self.isFlashModel(id: $0.0) }.map(\.1)
        let proQuotas = lower.filter { Self.isProModel(id: $0.0) }.map(\.1)

        let flashLiteMin = flashLiteQuotas.min(by: { $0.percentLeft < $1.percentLeft })
        let flashMin = flashQuotas.min(by: { $0.percentLeft < $1.percentLeft })
        let proMin = proQuotas.min(by: { $0.percentLeft < $1.percentLeft })

        // Keep missing tiers nil so downstream selectors can fall back to a quota the account actually reports.
        let primary: RateWindow? = proMin.map {
            RateWindow(
                usedPercent: 100 - $0.percentLeft,
                windowMinutes: 1440,
                resetsAt: $0.resetTime,
                resetDescription: $0.resetDescription)
        }

        let secondary: RateWindow? = flashMin.map {
            RateWindow(
                usedPercent: 100 - $0.percentLeft,
                windowMinutes: 1440,
                resetsAt: $0.resetTime,
                resetDescription: $0.resetDescription)
        }
        let tertiary: RateWindow? = flashLiteMin.map {
            RateWindow(
                usedPercent: 100 - $0.percentLeft,
                windowMinutes: 1440,
                resetsAt: $0.resetTime,
                resetDescription: $0.resetDescription)
        }

        let identity = ProviderIdentitySnapshot(
            providerID: .gemini,
            accountEmail: self.accountEmail,
            accountOrganization: nil,
            loginMethod: self.accountPlan)
        return UsageSnapshot(
            primary: primary,
            secondary: secondary,
            tertiary: tertiary,
            updatedAt: Date(),
            identity: identity)
    }

    private static func isFlashLiteModel(id: String) -> Bool {
        id.contains("flash-lite")
    }

    private static func isFlashModel(id: String) -> Bool {
        id.contains("flash") && !self.isFlashLiteModel(id: id)
    }

    private static func isProModel(id: String) -> Bool {
        id.contains("pro")
    }
}

public enum GeminiStatusProbeError: LocalizedError, Sendable, Equatable {
    case geminiNotInstalled
    case notLoggedIn
    case unsupportedAuthType(String)
    case consumerTierDeprecated
    case parseFailed(String)
    case timedOut
    case apiError(String)

    public var errorDescription: String? {
        switch self {
        case .geminiNotInstalled:
            "Gemini CLI is not installed or not on PATH."
        case .notLoggedIn:
            "Not logged in to Gemini. Run 'gemini' in Terminal to authenticate."
        case let .unsupportedAuthType(authType):
            "Gemini \(authType) auth not supported. Use Google account (OAuth) instead."
        case .consumerTierDeprecated:
            GeminiConsumerTierMigration.deprecationError
        case let .parseFailed(msg):
            "Could not parse Gemini usage: \(msg)"
        case .timedOut:
            "Gemini quota API request timed out."
        case let .apiError(msg):
            "Gemini API error: \(msg)"
        }
    }

    /// Detects Google's consumer-tier Gemini CLI shutdown responses.
    public static func isConsumerTierDeprecationSignal(_ text: String) -> Bool {
        let normalized = text.lowercased()
        if normalized.contains("unsupported_client") {
            return true
        }
        if normalized.contains("ineligibletiererror") {
            return true
        }
        if normalized.contains("no longer supported"), normalized.contains("gemini code assist") {
            return true
        }
        if normalized.contains("migrate"), normalized.contains("antigravity"), normalized.contains("gemini") {
            return true
        }
        return false
    }

    public static func throwIfConsumerTierDeprecated(data: Data) throws {
        guard let text = String(data: data, encoding: .utf8),
              isConsumerTierDeprecationSignal(text)
        else {
            return
        }
        throw GeminiStatusProbeError.consumerTierDeprecated
    }
}

public enum GeminiAuthType: String, Sendable {
    case oauthPersonal = "oauth-personal"
    case apiKey = "gemini-api-key"
    case vertexAI = "vertex-ai"
    case unknown
}

/// User tier IDs returned from the Cloud Code Private API (loadCodeAssist).
/// Maps to: google3/cloud/developer_experience/cloudcode/pa/service/usertier.go
public enum GeminiUserTierId: String, Sendable {
    case free = "free-tier"
    case legacy = "legacy-tier"
    case standard = "standard-tier"
}

public struct GeminiStatusProbe: Sendable {
    public var timeout: TimeInterval = 10.0
    public var homeDirectory: String
    public var dataLoader: @Sendable (URLRequest) async throws -> (Data, URLResponse)
    private static let log = CodexBarLog.logger(LogCategories.provider(.gemini, scope: "probe"))
    private static let quotaEndpoint = "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota"
    private static let loadCodeAssistEndpoint = "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist"
    private static let projectsEndpoint = "https://cloudresourcemanager.googleapis.com/v1/projects"
    private static let credentialsPath = "/.gemini/oauth_creds.json"
    private static let settingsPath = "/.gemini/settings.json"
    private static let tokenRefreshEndpoint = "https://oauth2.googleapis.com/token"

    public init(
        timeout: TimeInterval = 10.0,
        homeDirectory: String = NSHomeDirectory(),
        dataLoader: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse) = Self.defaultDataLoader)
    {
        self.timeout = timeout
        self.homeDirectory = homeDirectory
        self.dataLoader = dataLoader
    }

    /// Reads the current Gemini auth type from settings.json
    public static func currentAuthType(homeDirectory: String = NSHomeDirectory()) -> GeminiAuthType {
        let settingsURL = URL(fileURLWithPath: homeDirectory + Self.settingsPath)

        guard let data = try? Data(contentsOf: settingsURL),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let security = json["security"] as? [String: Any],
              let auth = security["auth"] as? [String: Any],
              let selectedType = auth["selectedType"] as? String
        else {
            return .unknown
        }

        if selectedType == "api-key" {
            return .apiKey
        }
        return GeminiAuthType(rawValue: selectedType) ?? .unknown
    }

    public func fetch() async throws -> GeminiStatusSnapshot {
        // Block explicitly unsupported auth types; allow unknown to try OAuth creds
        let authType = Self.currentAuthType(homeDirectory: self.homeDirectory)
        switch authType {
        case .apiKey:
            throw GeminiStatusProbeError.unsupportedAuthType("API key")
        case .vertexAI:
            throw GeminiStatusProbeError.unsupportedAuthType("Vertex AI")
        case .oauthPersonal, .unknown:
            break
        }

        let snap = try await Self.fetchViaAPI(
            timeout: self.timeout,
            homeDirectory: self.homeDirectory,
            dataLoader: self.dataLoader)

        Self.log.info("Gemini API fetch ok", metadata: [
            "dailyPercentLeft": "\(snap.dailyPercentLeft ?? -1)",
        ])
        return snap
    }

    // MARK: - Direct API approach

    private static func fetchViaAPI(
        timeout: TimeInterval,
        homeDirectory: String,
        dataLoader: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse)) async throws
        -> GeminiStatusSnapshot
    {
        let creds = try Self.loadCredentials(homeDirectory: homeDirectory)

        let expiryStr = creds.expiryDate.map { "\($0)" } ?? "nil"
        let hasRefresh = creds.refreshToken != nil
        Self.log.debug("Token check", metadata: [
            "expiry": expiryStr,
            "hasRefresh": hasRefresh ? "1" : "0",
            "now": "\(Date())",
        ])

        var accessToken = creds.accessToken?.isEmpty == false ? creds.accessToken : nil
        var idToken = creds.idToken
        let needsRefresh = accessToken == nil || creds.expiryDate.map { $0 < Date() } == true
        if needsRefresh {
            if accessToken == nil {
                Self.log.info("No access token found; attempting refresh from stored Gemini credentials")
            } else if let expiry = creds.expiryDate {
                Self.log.info("Token expired; attempting refresh", metadata: [
                    "expiry": "\(expiry)",
                ])
            }

            guard let refreshToken = creds.refreshToken, !refreshToken.isEmpty else {
                Self.log.error("No refresh token available")
                throw GeminiStatusProbeError.notLoggedIn
            }
            accessToken = try await Self.refreshAccessToken(
                refreshToken: refreshToken,
                timeout: timeout,
                homeDirectory: homeDirectory,
                dataLoader: dataLoader)
            idToken = (try? Self.loadCredentials(homeDirectory: homeDirectory).idToken) ?? idToken
        }
        guard let accessToken else {
            Self.log.error("No access token found")
            throw GeminiStatusProbeError.notLoggedIn
        }

        // Extract account info from JWT
        let claims = Self.extractClaimsFromToken(idToken)

        // Load Code Assist status to get project ID and tier (aligned with CLI setupUser logic)
        let caStatus = try await Self.loadCodeAssistStatus(
            accessToken: accessToken,
            timeout: timeout,
            dataLoader: dataLoader)

        // Determine the project ID to use for quota fetching.
        // Priority:
        // 1. Project ID returned by loadCodeAssist (e.g. managed project for free tier)
        // 2. Discovered project ID from cloud resource manager (e.g. user's own project)
        var projectId = caStatus.projectId
        if projectId == nil {
            projectId = try? await Self.discoverGeminiProjectId(
                accessToken: accessToken,
                timeout: timeout,
                dataLoader: dataLoader)
        }

        guard let url = URL(string: Self.quotaEndpoint) else {
            throw GeminiStatusProbeError.apiError("Invalid endpoint URL")
        }

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        // Include project ID for accurate quota
        if let projectId {
            request.httpBody = Data("{\"project\": \"\(projectId)\"}".utf8)
        } else {
            request.httpBody = Data("{}".utf8)
        }
        request.timeoutInterval = timeout

        let (data, response) = try await dataLoader(request)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw GeminiStatusProbeError.apiError("Invalid response")
        }

        if httpResponse.statusCode == 401 {
            try GeminiStatusProbeError.throwIfConsumerTierDeprecated(data: data)
            throw GeminiStatusProbeError.notLoggedIn
        }

        guard httpResponse.statusCode == 200 else {
            try GeminiStatusProbeError.throwIfConsumerTierDeprecated(data: data)
            throw GeminiStatusProbeError.apiError("HTTP \(httpResponse.statusCode)")
        }

        let snapshot = try Self.parseAPIResponse(data, email: claims.email)

        let plan = Self.resolveAccountPlan(
            tier: caStatus.tier,
            hostedDomain: claims.hostedDomain,
            paidTierName: caStatus.paidTierName)

        return GeminiStatusSnapshot(
            modelQuotas: snapshot.modelQuotas,
            rawText: snapshot.rawText,
            accountEmail: snapshot.accountEmail,
            accountPlan: plan)
    }

    private static func discoverGeminiProjectId(
        accessToken: String,
        timeout: TimeInterval,
        dataLoader: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse)) async throws
        -> String?
    {
        guard let url = URL(string: projectsEndpoint) else { return nil }

        var request = URLRequest(url: url)
        request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
        request.timeoutInterval = timeout

        let (data, response) = try await dataLoader(request)

        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            return nil
        }

        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let projects = json["projects"] as? [[String: Any]]
        else {
            return nil
        }

        // Look for Gemini API project (has "generative-language" label or "gen-lang-client" prefix)
        for project in projects {
            guard let projectId = project["projectId"] as? String else { continue }

            // Check for gen-lang-client prefix (Gemini CLI projects)
            if projectId.hasPrefix("gen-lang-client") {
                return projectId
            }

            // Check for generative-language label
            if let labels = project["labels"] as? [String: String],
               labels["generative-language"] != nil
            {
                return projectId
            }
        }

        return nil
    }

    private struct CodeAssistStatus {
        let tier: GeminiUserTierId?
        let projectId: String?
        let paidTierName: String?

        static let empty = CodeAssistStatus(tier: nil, projectId: nil, paidTierName: nil)
    }

    private static func loadCodeAssistStatus(
        accessToken: String,
        timeout: TimeInterval,
        dataLoader: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse)) async throws
        -> CodeAssistStatus
    {
        guard let url = URL(string: loadCodeAssistEndpoint) else {
            self.log.warning("loadCodeAssist: invalid endpoint URL")
            return .empty
        }

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = Data("{\"metadata\":{\"ideType\":\"GEMINI_CLI\",\"pluginType\":\"GEMINI\"}}".utf8)
        request.timeoutInterval = timeout

        let data: Data
        let response: URLResponse
        do {
            (data, response) = try await dataLoader(request)
        } catch {
            Self.log.warning("loadCodeAssist: request failed", metadata: ["error": "\(error)"])
            return .empty
        }

        guard let httpResponse = response as? HTTPURLResponse else {
            Self.log.warning("loadCodeAssist: invalid response type")
            return .empty
        }

        guard httpResponse.statusCode == 200 else {
            try GeminiStatusProbeError.throwIfConsumerTierDeprecated(data: data)
            Self.log.warning("loadCodeAssist: HTTP error", metadata: [
                "statusCode": "\(httpResponse.statusCode)",
                "body": String(data: data, encoding: .utf8) ?? "<binary>",
            ])
            return .empty
        }

        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            Self.log.warning("loadCodeAssist: failed to parse JSON", metadata: [
                "body": String(data: data, encoding: .utf8) ?? "<binary>",
            ])
            return .empty
        }

        let rawProjectId: String? = {
            if let project = json["cloudaicompanionProject"] as? String {
                return project
            }
            if let project = json["cloudaicompanionProject"] as? [String: Any] {
                if let projectId = project["id"] as? String {
                    return projectId
                }
                if let projectId = project["projectId"] as? String {
                    return projectId
                }
            }
            return nil
        }()
        let trimmedProjectId = rawProjectId?.trimmingCharacters(in: .whitespacesAndNewlines)
        let projectId = trimmedProjectId?.isEmpty == true ? nil : trimmedProjectId
        if let projectId {
            Self.log.info("loadCodeAssist: project detected", metadata: ["projectId": projectId])
        }

        let tierId = (json["currentTier"] as? [String: Any])?["id"] as? String
        let paidTierName = Self.parsePaidTierName(from: json)

        guard let tierId else {
            Self.log.warning("loadCodeAssist: no currentTier.id in response", metadata: [
                "json": "\(json)",
            ])
            return CodeAssistStatus(tier: nil, projectId: projectId, paidTierName: paidTierName)
        }

        guard let tier = GeminiUserTierId(rawValue: tierId) else {
            Self.log.warning("loadCodeAssist: unknown tier ID", metadata: ["tierId": tierId])
            return CodeAssistStatus(tier: nil, projectId: projectId, paidTierName: paidTierName)
        }

        Self.log.info("loadCodeAssist: success", metadata: [
            "tier": tierId,
            "projectId": projectId ?? "nil",
            "paidTierName": paidTierName ?? "nil",
        ])
        return CodeAssistStatus(tier: tier, projectId: projectId, paidTierName: paidTierName)
    }

    private struct OAuthCredentials {
        let accessToken: String?
        let idToken: String?
        let refreshToken: String?
        let expiryDate: Date?
    }

    fileprivate struct OAuthClientCredentials {
        let clientId: String
        let clientSecret: String
    }

    private static func isLikelyFnmManagedPath(_ path: String) -> Bool {
        let normalized = path.replacingOccurrences(of: "\\", with: "/")
        return normalized.contains("/fnm_multishells/")
            || (normalized.contains("/node-versions/") && normalized.contains("/fnm/"))
    }

    private static func resolveExecutableOnEnvironmentPath(
        named executable: String,
        environment: [String: String]) -> String?
    {
        guard let path = environment["PATH"] else { return nil }
        for directory in path.split(separator: ":") where !directory.isEmpty {
            let candidate = URL(fileURLWithPath: String(directory), isDirectory: true)
                .appendingPathComponent(executable)
                .path
            if FileManager.default.isExecutableFile(atPath: candidate) {
                return candidate
            }
        }
        return nil
    }

    private static func resolveGeminiPackageRootViaFnm(
        fnmPath: String,
        environment: [String: String]) -> String?
    {
        guard let currentVersion = runProcess(
            executable: fnmPath,
            arguments: ["current"],
            environment: environment,
            timeout: 2.0),
            !currentVersion.isEmpty
        else {
            return nil
        }

        // Prefer npm root -g because require.resolve searches from the current
        // working directory and often fails for globally-installed packages.
        if let npmRoot = runProcess(
            executable: fnmPath,
            arguments: [
                "exec",
                "--using",
                currentVersion,
                "npm",
                "root",
                "-g",
            ],
            environment: environment,
            timeout: 4.0),
            !npmRoot.isEmpty
        {
            let packageRoot = "\(npmRoot)/@google/gemini-cli"
            let packageJSONPath = "\(packageRoot)/package.json"
            if FileManager.default.fileExists(atPath: packageJSONPath) {
                return packageRoot
            }
        }

        // Fallback for non-npm global installations.
        if let packageJSONPath = runProcess(
            executable: fnmPath,
            arguments: [
                "exec",
                "--using",
                currentVersion,
                "node",
                "-p",
                "require.resolve('@google/gemini-cli/package.json')",
            ],
            environment: environment,
            timeout: 4.0),
            !packageJSONPath.isEmpty
        {
            return (packageJSONPath as NSString).deletingLastPathComponent
        }

        return nil
    }

    private static func findGeminiPackageRoot(startingAt path: String) -> String? {
        let fileManager = FileManager.default
        var currentURL = URL(fileURLWithPath: path).standardizedFileURL

        var isDirectory: ObjCBool = false
        if !fileManager.fileExists(atPath: currentURL.path, isDirectory: &isDirectory) || !isDirectory.boolValue {
            currentURL.deleteLastPathComponent()
        }

        // Bound the walk so an unrelated Gemini install elsewhere on the host
        // (e.g. a global npm/brew install unrelated to the resolved binary) can't
        // contaminate discovery started from the actual binary path.
        let maxAscents = 8
        for _ in 0...maxAscents {
            let packageJSONURL = currentURL.appendingPathComponent("package.json")
            if let data = try? Data(contentsOf: packageJSONURL),
               let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
               json["name"] as? String == "@google/gemini-cli"
            {
                return currentURL.path
            }

            // Also check for a global Node installation layout:
            // <current>/lib/node_modules/@google/gemini-cli/package.json
            let globalPackageJSONURL = currentURL
                .appendingPathComponent("lib")
                .appendingPathComponent("node_modules")
                .appendingPathComponent("@google")
                .appendingPathComponent("gemini-cli")
                .appendingPathComponent("package.json")
            if let data = try? Data(contentsOf: globalPackageJSONURL),
               let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
               json["name"] as? String == "@google/gemini-cli"
            {
                return globalPackageJSONURL.deletingLastPathComponent().path
            }

            // Homebrew layout:
            // <cellar-version>/libexec/lib/node_modules/@google/gemini-cli/package.json
            let homebrewPackageJSONURL = currentURL
                .appendingPathComponent("libexec")
                .appendingPathComponent("lib")
                .appendingPathComponent("node_modules")
                .appendingPathComponent("@google")
                .appendingPathComponent("gemini-cli")
                .appendingPathComponent("package.json")
            if let data = try? Data(contentsOf: homebrewPackageJSONURL),
               let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
               json["name"] as? String == "@google/gemini-cli"
            {
                return homebrewPackageJSONURL.deletingLastPathComponent().path
            }

            let parentURL = currentURL.deletingLastPathComponent()
            if parentURL.path == currentURL.path {
                return nil
            }
            currentURL = parentURL
        }

        return nil
    }

    private static func extractOAuthCredentials(fromGeminiPackageRoot packageRoot: String) -> OAuthClientCredentials? {
        // Check the standard distributed file first, then any sibling core package
        let oauthFile = "dist/src/code_assist/oauth2.js"
        let candidatePaths = [
            "\(packageRoot)/\(oauthFile)",
            "\(packageRoot)/node_modules/@google/gemini-cli-core/\(oauthFile)",
        ]

        for path in candidatePaths {
            if let content = try? String(contentsOfFile: path, encoding: .utf8),
               let credentials = Self.parseOAuthCredentials(from: content)
            {
                return credentials
            }
        }

        return Self.extractOAuthCredentialsFromBundle(packageRoot: packageRoot)
    }

    private static func extractOAuthCredentialsFromBundle(packageRoot: String) -> OAuthClientCredentials? {
        let bundleRoot = URL(fileURLWithPath: packageRoot).appendingPathComponent("bundle", isDirectory: true)
        let entryURL = bundleRoot.appendingPathComponent("gemini.js")

        guard FileManager.default.fileExists(atPath: entryURL.path) else {
            return nil
        }

        var pendingURLs = [entryURL]
        var visitedPaths = Set<String>()

        while !pendingURLs.isEmpty {
            let currentURL = pendingURLs.removeFirst()
            let standardizedPath = currentURL.standardizedFileURL.path
            guard visitedPaths.insert(standardizedPath).inserted,
                  let content = try? String(contentsOf: currentURL, encoding: .utf8)
            else {
                continue
            }

            if let credentials = Self.parseOAuthCredentials(from: content) {
                return credentials
            }

            let imports = Self.extractRelativeJavaScriptImports(from: content)
            for importPath in imports {
                let nextURL = URL(fileURLWithPath: importPath, relativeTo: currentURL.deletingLastPathComponent())
                    .standardizedFileURL
                guard nextURL.path.hasPrefix(bundleRoot.path) else { continue }
                pendingURLs.append(nextURL)
            }
        }

        guard let bundleFiles = try? FileManager.default.contentsOfDirectory(
            at: bundleRoot,
            includingPropertiesForKeys: nil,
            options: [.skipsHiddenFiles])
        else {
            return nil
        }

        for url in bundleFiles where url.pathExtension == "js" && !visitedPaths.contains(url.standardizedFileURL.path) {
            guard let content = try? String(contentsOf: url, encoding: .utf8) else { continue }
            if let credentials = Self.parseOAuthCredentials(from: content) {
                return credentials
            }
        }

        return nil
    }

    private static func extractRelativeJavaScriptImports(from content: String) -> [String] {
        let patterns = [
            #"(?:import|export)\s+(?:[^;]*?\s+from\s+)?[\"'](\./[^\"']+\.js)[\"']"#,
            #"import\(\s*[\"'](\./[^\"']+\.js)[\"']\s*\)"#,
        ]

        var discoveredPaths: [String] = []
        var seen = Set<String>()
        let fullRange = NSRange(content.startIndex..., in: content)

        for pattern in patterns {
            guard let regex = try? NSRegularExpression(pattern: pattern) else { continue }
            for match in regex.matches(in: content, range: fullRange) {
                guard let range = Range(match.range(at: 1), in: content) else { continue }
                let path = String(content[range])
                if seen.insert(path).inserted {
                    discoveredPaths.append(path)
                }
            }
        }

        return discoveredPaths
    }

    private static func extractOAuthCredentialsFromLegacyPaths(realGeminiPath: String) -> OAuthClientCredentials? {
        let binDir = (realGeminiPath as NSString).deletingLastPathComponent
        let baseDir = (binDir as NSString).deletingLastPathComponent

        let oauthSubpath =
            "node_modules/@google/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js"
        let nixShareSubpath =
            "share/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js"
        let oauthFile = "dist/src/code_assist/oauth2.js"
        let possiblePaths = [
            // Homebrew nested structure
            "\(baseDir)/libexec/lib/\(oauthSubpath)",
            "\(baseDir)/lib/\(oauthSubpath)",
            // Nix package layout
            "\(baseDir)/\(nixShareSubpath)",
            // Bun/npm sibling structure: gemini-cli-core is a sibling to gemini-cli
            "\(baseDir)/../gemini-cli-core/\(oauthFile)",
            // npm nested inside gemini-cli
            "\(baseDir)/node_modules/@google/gemini-cli-core/\(oauthFile)",
        ]

        for path in possiblePaths {
            if let content = try? String(contentsOfFile: path, encoding: .utf8),
               let credentials = Self.parseOAuthCredentials(from: content)
            {
                return credentials
            }
        }

        return nil
    }

    private static func parseOAuthCredentials(from content: String) -> OAuthClientCredentials? {
        // Match: const/let/var OAUTH_CLIENT_ID = '...';
        let clientIdPattern = #"(?:const|let|var)?\s*OAUTH_CLIENT_ID\s*=\s*['"]([\w\-\.]+)['"]\s*;"#
        let secretPattern = #"(?:const|let|var)?\s*OAUTH_CLIENT_SECRET\s*=\s*['"]([\w\-]+)['"]\s*;"#

        guard let clientIdRegex = try? NSRegularExpression(pattern: clientIdPattern),
              let secretRegex = try? NSRegularExpression(pattern: secretPattern)
        else {
            return nil
        }

        let range = NSRange(content.startIndex..., in: content)

        guard let clientIdMatch = clientIdRegex.firstMatch(in: content, range: range),
              let clientIdRange = Range(clientIdMatch.range(at: 1), in: content),
              let secretMatch = secretRegex.firstMatch(in: content, range: range),
              let secretRange = Range(secretMatch.range(at: 1), in: content)
        else {
            return nil
        }

        let clientId = String(content[clientIdRange])
        let clientSecret = String(content[secretRange])

        return OAuthClientCredentials(clientId: clientId, clientSecret: clientSecret)
    }

    private static func refreshAccessToken(
        refreshToken: String,
        timeout: TimeInterval,
        homeDirectory: String,
        dataLoader: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse)) async throws
        -> String
    {
        guard let url = URL(string: tokenRefreshEndpoint) else {
            throw GeminiStatusProbeError.apiError("Invalid token refresh URL")
        }

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.timeoutInterval = timeout

        guard let oauthCreds = Self.extractOAuthCredentials() else {
            Self.log.error("Could not extract OAuth credentials from Gemini CLI")
            throw GeminiStatusProbeError.apiError(GeminiConsumerTierMigration.oauthRecoveryError)
        }

        let body = [
            "client_id=\(oauthCreds.clientId)",
            "client_secret=\(oauthCreds.clientSecret)",
            "refresh_token=\(refreshToken)",
            "grant_type=refresh_token",
        ].joined(separator: "&")
        request.httpBody = body.data(using: .utf8)

        let (data, response) = try await dataLoader(request)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw GeminiStatusProbeError.apiError("Invalid refresh response")
        }

        guard httpResponse.statusCode == 200 else {
            try GeminiStatusProbeError.throwIfConsumerTierDeprecated(data: data)
            Self.log.error("Token refresh failed", metadata: [
                "statusCode": "\(httpResponse.statusCode)",
            ])
            throw GeminiStatusProbeError.notLoggedIn
        }

        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let newAccessToken = json["access_token"] as? String
        else {
            throw GeminiStatusProbeError.parseFailed("Could not parse refresh response")
        }

        // Update stored credentials with new token
        try Self.updateStoredCredentials(json, homeDirectory: homeDirectory)

        Self.log.info("Token refreshed successfully")
        return newAccessToken
    }

    private static func updateStoredCredentials(_ refreshResponse: [String: Any], homeDirectory: String) throws {
        let credsURL = URL(fileURLWithPath: homeDirectory + Self.credentialsPath)

        guard let existingCreds = try? Data(contentsOf: credsURL),
              var json = try? JSONSerialization.jsonObject(with: existingCreds) as? [String: Any]
        else {
            return
        }

        // Update with new values from refresh response
        if let accessToken = refreshResponse["access_token"] {
            json["access_token"] = accessToken
        }
        if let expiresIn = refreshResponse["expires_in"] as? Double {
            json["expiry_date"] = (Date().timeIntervalSince1970 + expiresIn) * 1000
        }
        if let idToken = refreshResponse["id_token"] {
            json["id_token"] = idToken
        }

        let updatedData = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted])
        try updatedData.write(to: credsURL, options: .atomic)
    }

    private static func loadCredentials(homeDirectory: String) throws -> OAuthCredentials {
        let credsURL = URL(fileURLWithPath: homeDirectory + Self.credentialsPath)

        guard FileManager.default.fileExists(atPath: credsURL.path) else {
            throw GeminiStatusProbeError.notLoggedIn
        }

        let data = try Data(contentsOf: credsURL)
        guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            throw GeminiStatusProbeError.parseFailed("Invalid credentials file")
        }

        let accessToken = json["access_token"] as? String
        let idToken = json["id_token"] as? String
        let refreshToken = json["refresh_token"] as? String

        var expiryDate: Date?
        if let expiryMs = json["expiry_date"] as? Double {
            expiryDate = Date(timeIntervalSince1970: expiryMs / 1000)
        }

        return OAuthCredentials(
            accessToken: accessToken,
            idToken: idToken,
            refreshToken: refreshToken,
            expiryDate: expiryDate)
    }

    private struct TokenClaims {
        let email: String?
        let hostedDomain: String?
    }

    private static func extractClaimsFromToken(_ idToken: String?) -> TokenClaims {
        guard let token = idToken else { return TokenClaims(email: nil, hostedDomain: nil) }

        let parts = token.components(separatedBy: ".")
        guard parts.count >= 2 else { return TokenClaims(email: nil, hostedDomain: nil) }

        var payload = parts[1]
            .replacingOccurrences(of: "-", with: "+")
            .replacingOccurrences(of: "_", with: "/")

        let remainder = payload.count % 4
        if remainder > 0 {
            payload += String(repeating: "=", count: 4 - remainder)
        }

        guard let data = Data(base64Encoded: payload, options: .ignoreUnknownCharacters),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
        else {
            return TokenClaims(email: nil, hostedDomain: nil)
        }

        return TokenClaims(
            email: json["email"] as? String,
            hostedDomain: json["hd"] as? String)
    }

    private static func extractEmailFromToken(_ idToken: String?) -> String? {
        self.extractClaimsFromToken(idToken).email
    }

    private struct QuotaBucket: Decodable {
        let remainingFraction: Double?
        let resetTime: String?
        let modelId: String?
        let tokenType: String?
    }

    private struct QuotaResponse: Decodable {
        let buckets: [QuotaBucket]?
    }

    private static func parseAPIResponse(_ data: Data, email: String?) throws -> GeminiStatusSnapshot {
        let decoder = JSONDecoder()
        let response = try decoder.decode(QuotaResponse.self, from: data)

        guard let buckets = response.buckets, !buckets.isEmpty else {
            throw GeminiStatusProbeError.parseFailed("No quota buckets in response")
        }

        // Group quotas by model, keeping lowest per model (input tokens usually)
        var modelQuotaMap: [String: (fraction: Double, resetString: String?)] = [:]

        for bucket in buckets {
            guard let modelId = bucket.modelId, let fraction = bucket.remainingFraction else { continue }

            if let existing = modelQuotaMap[modelId] {
                if fraction < existing.fraction {
                    modelQuotaMap[modelId] = (fraction, bucket.resetTime)
                }
            } else {
                modelQuotaMap[modelId] = (fraction, bucket.resetTime)
            }
        }

        // Convert to sorted array (by model name for consistent ordering)
        let quotas = modelQuotaMap
            .sorted { $0.key < $1.key }
            .map { modelId, info in
                let resetDate = info.resetString.flatMap { Self.parseResetTime($0) }
                return GeminiModelQuota(
                    modelId: modelId,
                    percentLeft: info.fraction * 100,
                    resetTime: resetDate,
                    resetDescription: info.resetString.flatMap { Self.formatResetTime($0) })
            }

        let rawText = String(data: data, encoding: .utf8) ?? ""

        return GeminiStatusSnapshot(
            modelQuotas: quotas,
            rawText: rawText,
            accountEmail: email,
            accountPlan: nil)
    }

    private static func parseResetTime(_ isoString: String) -> Date? {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

        if let date = formatter.date(from: isoString) {
            return date
        }
        formatter.formatOptions = [.withInternetDateTime]
        return formatter.date(from: isoString)
    }

    private static func formatResetTime(_ isoString: String) -> String {
        guard let resetDate = parseResetTime(isoString) else {
            return "Resets soon"
        }

        let now = Date()
        let interval = resetDate.timeIntervalSince(now)

        if interval <= 0 {
            return "Resets soon"
        }

        let hours = Int(interval / 3600)
        let minutes = Int((interval.truncatingRemainder(dividingBy: 3600)) / 60)

        if hours > 0 {
            return "Resets in \(hours)h \(minutes)m"
        } else {
            return "Resets in \(minutes)m"
        }
    }

    // MARK: - Legacy CLI parsing (kept for fallback)

    public static func parse(text: String) throws -> GeminiStatusSnapshot {
        let clean = TextParsing.stripANSICodes(text)
        guard !clean.isEmpty else { throw GeminiStatusProbeError.timedOut }

        let quotas = Self.parseModelUsageTable(clean)

        if quotas.isEmpty {
            if clean.contains("Login with Google") || clean.contains("Use Gemini API key") {
                throw GeminiStatusProbeError.notLoggedIn
            }
            if clean.contains("Waiting for auth"), !clean.contains("Usage") {
                throw GeminiStatusProbeError.notLoggedIn
            }
            throw GeminiStatusProbeError.parseFailed("No usage data found in /stats output")
        }

        return GeminiStatusSnapshot(
            modelQuotas: quotas,
            rawText: text,
            accountEmail: nil,
            accountPlan: nil)
    }

    private static func parseModelUsageTable(_ text: String) -> [GeminiModelQuota] {
        let lines = text.components(separatedBy: .newlines)
        var quotas: [GeminiModelQuota] = []

        let pattern = #"(gemini[-\w.]+)\s+[\d-]+\s+([0-9]+(?:\.[0-9]+)?)\s*%\s*\(([^)]+)\)"#
        guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else {
            return []
        }

        for line in lines {
            let cleanLine = line.replacingOccurrences(of: "│", with: " ")
            let range = NSRange(cleanLine.startIndex..<cleanLine.endIndex, in: cleanLine)
            guard let match = regex.firstMatch(in: cleanLine, options: [], range: range),
                  match.numberOfRanges >= 4 else { continue }

            guard let modelRange = Range(match.range(at: 1), in: cleanLine),
                  let pctRange = Range(match.range(at: 2), in: cleanLine),
                  let pct = Double(cleanLine[pctRange])
            else { continue }

            let modelId = String(cleanLine[modelRange])
            var resetDesc: String?
            if let resetRange = Range(match.range(at: 3), in: cleanLine) {
                resetDesc = String(cleanLine[resetRange]).trimmingCharacters(in: .whitespaces)
            }

            quotas.append(GeminiModelQuota(
                modelId: modelId,
                percentLeft: pct,
                resetTime: nil,
                resetDescription: resetDesc))
        }

        return quotas
    }
}

extension GeminiStatusProbe {
    fileprivate static func extractOAuthCredentials() -> OAuthClientCredentials? {
        if let resolved = GeminiOAuthConfig.environmentClient() {
            return OAuthClientCredentials(clientId: resolved.clientID, clientSecret: resolved.clientSecret)
        }
        if let path = GeminiOAuthConfig.configuredOAuth2JSPath,
           let credentials = Self.parseOAuthCredentials(fromFile: path)
        {
            return credentials
        }
        if let credentials = Self.discoverOAuthCredentialsFromInstalledCLI() {
            return OAuthClientCredentials(clientId: credentials.clientID, clientSecret: credentials.clientSecret)
        }
        if let credentials = Self.discoverOAuthCredentialsFromKnownInstallPaths() {
            return credentials
        }
        return nil
    }

    /// Optional Homebrew/npm prefixes for last-resort discovery when the gemini
    /// binary cannot be resolved (GUI PATH / sandbox). Tests inject fake Cellar trees.
    @TaskLocal public static var knownInstallPrefixesForTesting: [String]?

    fileprivate static func discoverOAuthCredentialsFromKnownInstallPaths() -> OAuthClientCredentials? {
        let oauthFile = "dist/src/code_assist/oauth2.js"
        let nestedOAuthFile =
            "node_modules/@google/gemini-cli/node_modules/@google/gemini-cli-core/\(oauthFile)"
        let home = FileManager.default.homeDirectoryForCurrentUser.path

        // When tests inject Homebrew prefixes, skip the hard-coded host paths so
        // fixture Cellar trees are not shadowed by the developer's real install.
        if Self.knownInstallPrefixesForTesting == nil {
            let possiblePaths = [
                "/opt/homebrew/lib/node_modules/@google/gemini-cli-core/\(oauthFile)",
                "/opt/homebrew/lib/\(nestedOAuthFile)",
                "/usr/local/lib/node_modules/@google/gemini-cli-core/\(oauthFile)",
                "/usr/local/lib/\(nestedOAuthFile)",
                "\(home)/.npm-global/lib/node_modules/@google/gemini-cli-core/\(oauthFile)",
                "\(home)/.npm-global/lib/\(nestedOAuthFile)",
            ]

            for path in possiblePaths {
                if let credentials = Self.parseOAuthCredentials(fromFile: path) {
                    return credentials
                }
            }
        }

        // Homebrew Cellar/opt libexec layouts keep credentials inside the package
        // bundle when GUI PATH cannot resolve `/opt/homebrew/bin/gemini`.
        for packageRoot in Self.knownHomebrewGeminiPackageRoots() {
            if let credentials = Self.extractOAuthCredentials(fromGeminiPackageRoot: packageRoot) {
                return credentials
            }
        }
        return nil
    }

    fileprivate static func knownHomebrewPrefixes() -> [String] {
        if let overrides = self.knownInstallPrefixesForTesting, !overrides.isEmpty {
            return overrides
        }
        return ["/opt/homebrew", "/usr/local"]
    }

    fileprivate static func knownHomebrewGeminiPackageRoots(
        fileManager: FileManager = .default) -> [String]
    {
        var roots: [String] = []
        var seen = Set<String>()

        func appendPackageRoot(under prefixRoot: String) {
            let packageRoot = URL(fileURLWithPath: prefixRoot)
                .appendingPathComponent("libexec")
                .appendingPathComponent("lib")
                .appendingPathComponent("node_modules")
                .appendingPathComponent("@google")
                .appendingPathComponent("gemini-cli")
                .path
            guard fileManager.fileExists(atPath: packageRoot),
                  seen.insert(packageRoot).inserted
            else {
                return
            }
            roots.append(packageRoot)
        }

        for prefix in Self.knownHomebrewPrefixes() {
            let optRoot = "\(prefix)/opt/gemini-cli"
            if fileManager.fileExists(atPath: optRoot) {
                appendPackageRoot(under: optRoot)
            }

            let cellarRoot = "\(prefix)/Cellar/gemini-cli"
            guard let versions = try? fileManager.contentsOfDirectory(atPath: cellarRoot) else {
                continue
            }
            for version in versions.sorted() {
                appendPackageRoot(under: "\(cellarRoot)/\(version)")
            }
        }

        return roots
    }

    fileprivate static func parseOAuthCredentials(fromFile path: String) -> OAuthClientCredentials? {
        guard let content = try? String(contentsOfFile: path, encoding: .utf8) else {
            return nil
        }
        return Self.parseOAuthCredentials(from: content)
    }

    fileprivate static func discoverOAuthCredentialsFromInstalledCLI() -> GeminiOAuthConfig.ClientCredentials? {
        let env = ProcessInfo.processInfo.environment

        guard let geminiPath = BinaryLocator.resolveGeminiBinary(
            env: env,
            loginPATH: LoginShellPathCache.shared.current)
            ?? TTYCommandRunner.which("gemini")
        else {
            return nil
        }

        let resolvedGeminiPath = URL(fileURLWithPath: geminiPath).resolvingSymlinksInPath().path

        if let credentials = Self.extractOAuthCredentialsFromLegacyPaths(realGeminiPath: resolvedGeminiPath) {
            return GeminiOAuthConfig.ClientCredentials(
                clientID: credentials.clientId,
                clientSecret: credentials.clientSecret)
        }

        if Self.isLikelyFnmManagedPath(geminiPath) || Self.isLikelyFnmManagedPath(resolvedGeminiPath),
           let fnmPath = Self.resolveExecutableOnEnvironmentPath(named: "fnm", environment: env)
           ?? TTYCommandRunner.which("fnm"),
           let packageRoot = Self.resolveGeminiPackageRootViaFnm(fnmPath: fnmPath, environment: env),
           let credentials = Self.extractOAuthCredentials(fromGeminiPackageRoot: packageRoot)
        {
            return GeminiOAuthConfig.ClientCredentials(
                clientID: credentials.clientId,
                clientSecret: credentials.clientSecret)
        }

        if let packageRoot = Self.findGeminiPackageRoot(startingAt: resolvedGeminiPath),
           let credentials = Self.extractOAuthCredentials(fromGeminiPackageRoot: packageRoot)
        {
            return GeminiOAuthConfig.ClientCredentials(
                clientID: credentials.clientId,
                clientSecret: credentials.clientSecret)
        }

        return nil
    }

    /// Plan display strings with tier mapping:
    /// - paidTier.name: Most-specific paid subscription label from Google, regardless of currentTier
    /// - standard-tier: Paid subscription fallback (Code Assist Standard/Enterprise, Developer Program Premium)
    /// - free-tier + hd claim: Workspace account (Gemini included free since Jan 2025)
    /// - free-tier: Personal free account
    /// - legacy-tier: Unknown legacy/grandfathered tier
    /// - nil (API failed): Leave blank (no display)
    fileprivate static func resolveAccountPlan(
        tier: GeminiUserTierId?,
        hostedDomain: String?,
        paidTierName: String?) -> String?
    {
        // Match Gemini CLI's contract: a named paid tier is the most specific plan signal,
        // even when currentTier is missing, unknown, or still reports free-tier.
        if let paidTierName {
            self.log.info("Paid tier detected", metadata: [
                "tier": tier?.rawValue ?? "unknown",
                "plan": paidTierName,
            ])
            return paidTierName
        }

        switch (tier, hostedDomain) {
        case (.standard, _):
            return "Paid"
        case let (.free, .some(domain)):
            Self.log.info("Workspace account detected", metadata: ["domain": domain])
            return "Workspace"
        case (.free, .none):
            Self.log.info("Personal free account")
            return "Free"
        case (.legacy, _):
            return "Legacy"
        case (.none, _):
            self.log.info("Tier detection failed, leaving plan blank")
            return nil
        }
    }

    private static func parsePaidTierName(from json: [String: Any]) -> String? {
        guard let paidTier = json["paidTier"] as? [String: Any],
              let rawName = paidTier["name"] as? String
        else {
            return nil
        }
        let trimmed = rawName.trimmingCharacters(in: .whitespacesAndNewlines)
        return trimmed.isEmpty ? nil : trimmed
    }
}

extension GeminiStatusProbe {
    private static let processTimeoutQueue = DispatchQueue(
        label: "com.steipete.codexbar.gemini-process-timeout",
        qos: .utility,
        attributes: .concurrent)

    package static func runProcess(
        executable: String,
        arguments: [String],
        environment: [String: String],
        timeout: TimeInterval) -> String?
    {
        let process = Process()
        process.executableURL = URL(fileURLWithPath: executable)
        process.arguments = arguments

        var mergedEnvironment = environment
        mergedEnvironment["PATH"] = PathBuilder.effectivePATH(
            purposes: [.tty, .nodeTooling],
            env: environment,
            loginPATH: LoginShellPathCache.shared.current)
        process.environment = mergedEnvironment

        let stdout = Pipe()
        let stderr = Pipe()
        process.standardOutput = stdout
        process.standardError = stderr
        process.standardInput = nil

        let stdoutCapture = ProcessPipeCapture(pipe: stdout)
        let stderrCapture = ProcessPipeCapture(pipe: stderr)

        do {
            try process.run()
        } catch {
            stdoutCapture.stop()
            stdout.fileHandleForWriting.closeFile()
            stderrCapture.stop()
            stderr.fileHandleForWriting.closeFile()
            return nil
        }
        // Process has duplicated these descriptors. Closing the parent's copies lets readers observe EOF as
        // soon as the helper exits instead of spending the full drain grace period on every successful call.
        stdout.fileHandleForWriting.closeFile()
        stderr.fileHandleForWriting.closeFile()
        stdoutCapture.start()
        stderrCapture.start()
        let pid = process.processIdentifier
        let processGroup: pid_t? = setpgid(pid, pid) == 0 ? pid : nil

        // Wait synchronously for Foundation to reap the helper. A separate timer owns timeout termination,
        // so neither termination-handler scheduling nor stale Process.isRunning state controls normal exits.
        let timeoutState = GeminiProcessTimeoutState()
        let timeoutTimer = DispatchSource.makeTimerSource(queue: Self.processTimeoutQueue)
        timeoutTimer.schedule(deadline: .now() + max(0, timeout))
        timeoutTimer.setEventHandler {
            guard process.isRunning else { return }
            timeoutState.markTimedOut()
            SubprocessRunner.terminateProcess(process, processGroup: processGroup)
        }
        timeoutTimer.resume()
        process.waitUntilExit()
        timeoutTimer.cancel()

        if timeoutState.didTimeOut {
            stdoutCapture.stop()
            stderrCapture.stop()
            return nil
        }

        let data = stdoutCapture.finishFirstLineSynchronously(timeout: 1)
        stderrCapture.stop()
        let output = ProcessPipeCapture.decodeUTF8(data)
            .trimmingCharacters(in: .whitespacesAndNewlines)
        guard process.terminationStatus == 0, !output.isEmpty else {
            return nil
        }

        return output.components(separatedBy: .newlines).first?
            .trimmingCharacters(in: .whitespacesAndNewlines)
    }
}

private final class GeminiProcessTimeoutState: @unchecked Sendable {
    private let lock = NSLock()
    private var timedOut = false

    var didTimeOut: Bool {
        self.lock.lock()
        defer { self.lock.unlock() }
        return self.timedOut
    }

    func markTimedOut() {
        self.lock.lock()
        self.timedOut = true
        self.lock.unlock()
    }
}
