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

public struct KiloUsageSnapshot: Sendable {
    public let creditsUsed: Double?
    public let creditsTotal: Double?
    public let creditsRemaining: Double?
    public let passUsed: Double?
    public let passTotal: Double?
    public let passRemaining: Double?
    public let passBonus: Double?
    public let passResetsAt: Date?
    public let planName: String?
    public let autoTopUpEnabled: Bool?
    public let autoTopUpMethod: String?
    public let updatedAt: Date

    public init(
        creditsUsed: Double?,
        creditsTotal: Double?,
        creditsRemaining: Double?,
        passUsed: Double? = nil,
        passTotal: Double? = nil,
        passRemaining: Double? = nil,
        passBonus: Double? = nil,
        passResetsAt: Date? = nil,
        planName: String?,
        autoTopUpEnabled: Bool?,
        autoTopUpMethod: String?,
        updatedAt: Date)
    {
        self.creditsUsed = creditsUsed
        self.creditsTotal = creditsTotal
        self.creditsRemaining = creditsRemaining
        self.passUsed = passUsed
        self.passTotal = passTotal
        self.passRemaining = passRemaining
        self.passBonus = passBonus
        self.passResetsAt = passResetsAt
        self.planName = planName
        self.autoTopUpEnabled = autoTopUpEnabled
        self.autoTopUpMethod = autoTopUpMethod
        self.updatedAt = updatedAt
    }

    public func toUsageSnapshot() -> UsageSnapshot {
        let total = self.resolvedTotal
        let used = self.resolvedUsed

        let primary: RateWindow?
        if let total {
            let usedPercent: Double = if total > 0 {
                min(100, max(0, (used / total) * 100))
            } else {
                // Preserve a visible exhausted state for valid zero-total snapshots.
                100
            }
            let usedText = Self.compactNumber(used)
            let totalText = Self.compactNumber(total)
            primary = RateWindow(
                usedPercent: usedPercent,
                windowMinutes: nil,
                resetsAt: nil,
                resetDescription: "\(usedText)/\(totalText) credits")
        } else {
            primary = nil
        }

        let loginMethod = Self.makeLoginMethod(
            planName: self.planName,
            autoTopUpEnabled: self.autoTopUpEnabled,
            autoTopUpMethod: self.autoTopUpMethod)

        return UsageSnapshot(
            primary: primary,
            secondary: self.passWindow,
            tertiary: nil,
            providerCost: nil,
            updatedAt: self.updatedAt,
            identity: ProviderIdentitySnapshot(
                providerID: .kilo,
                accountEmail: nil,
                accountOrganization: nil,
                loginMethod: loginMethod))
    }

    private var resolvedTotal: Double? {
        if let creditsTotal { return max(0, creditsTotal) }
        if let creditsUsed, let creditsRemaining {
            return max(0, creditsUsed + creditsRemaining)
        }
        return nil
    }

    private var resolvedUsed: Double {
        if let creditsUsed {
            return max(0, creditsUsed)
        }
        if let total = self.resolvedTotal,
           let creditsRemaining
        {
            return max(0, total - creditsRemaining)
        }
        return 0
    }

    private var resolvedPassTotal: Double? {
        if let passTotal { return max(0, passTotal) }
        if let passUsed, let passRemaining {
            return max(0, passUsed + passRemaining)
        }
        return nil
    }

    private var resolvedPassUsed: Double {
        if let passUsed {
            return max(0, passUsed)
        }
        if let total = self.resolvedPassTotal,
           let passRemaining
        {
            return max(0, total - passRemaining)
        }
        return 0
    }

    private var passWindow: RateWindow? {
        guard let total = self.resolvedPassTotal else {
            return nil
        }

        let used = self.resolvedPassUsed
        let bonus = max(0, self.passBonus ?? 0)
        let baseCredits = max(0, total - bonus)
        let usedPercent: Double = if total > 0 {
            min(100, max(0, (used / total) * 100))
        } else {
            100
        }

        var detail = "$\(Self.currencyNumber(used)) / $\(Self.currencyNumber(baseCredits))"
        if bonus > 0 {
            detail += " (+ $\(Self.currencyNumber(bonus)) bonus)"
        }

        return RateWindow(
            usedPercent: usedPercent,
            windowMinutes: nil,
            resetsAt: self.passResetsAt,
            resetDescription: detail)
    }

    private static func compactNumber(_ value: Double) -> String {
        if value.rounded(.towardZero) == value {
            return String(Int(value))
        }
        return String(format: "%.2f", value)
    }

    private static func currencyNumber(_ value: Double) -> String {
        String(format: "%.2f", max(0, value))
    }

    private static func makeLoginMethod(
        planName: String?,
        autoTopUpEnabled: Bool?,
        autoTopUpMethod: String?) -> String?
    {
        var parts: [String] = []

        if let planName = Self.trimmed(planName) {
            parts.append(planName)
        }

        if let autoTopUpEnabled {
            if autoTopUpEnabled {
                if let method = Self.trimmed(autoTopUpMethod) {
                    parts.append("Auto top-up: \(method)")
                } else {
                    parts.append("Auto top-up: enabled")
                }
            } else {
                parts.append("Auto top-up: off")
            }
        }

        return parts.isEmpty ? nil : parts.joined(separator: " · ")
    }

    private static func trimmed(_ raw: String?) -> String? {
        guard let raw else { return nil }
        let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
        return trimmed.isEmpty ? nil : trimmed
    }
}

public enum KiloUsageError: LocalizedError, Sendable, Equatable {
    case missingCredentials
    case cliSessionMissing(String)
    case cliSessionUnreadable(String)
    case cliSessionInvalid(String)
    case unauthorized
    case endpointNotFound
    case serviceUnavailable(Int)
    case networkError(String)
    case parseFailed(String)
    case apiError(Int)

    public var errorDescription: String? {
        switch self {
        case .missingCredentials:
            "Kilo API credentials missing. Set KILO_API_KEY."
        case let .cliSessionMissing(path):
            "Kilo CLI session not found at \(path). Run `kilo login` to create ~/.local/share/kilo/auth.json."
        case let .cliSessionUnreadable(path):
            "Kilo CLI session file is unreadable at \(path). Fix permissions or run `kilo login` again."
        case let .cliSessionInvalid(path):
            "Kilo CLI session file is invalid at \(path). Run `kilo login` to refresh auth.json."
        case .unauthorized:
            "Kilo authentication failed (401/403). Refresh KILO_API_KEY or run `kilo login`."
        case .endpointNotFound:
            "Kilo API endpoint not found (404). Verify the tRPC batch path and procedure names."
        case let .serviceUnavailable(statusCode):
            "Kilo API is currently unavailable (HTTP \(statusCode)). Try again later."
        case let .networkError(message):
            "Kilo network error: \(message)"
        case .parseFailed:
            "Failed to parse Kilo API response. Response format may have changed."
        case let .apiError(statusCode):
            "Kilo API request failed (HTTP \(statusCode))."
        }
    }
}

// swiftlint:disable:next type_body_length
public struct KiloUsageFetcher: Sendable {
    private struct KiloPassFields {
        let used: Double?
        let total: Double?
        let remaining: Double?
        let bonus: Double?
        let resetsAt: Date?
    }

    static let procedures = [
        "user.getCreditBlocks",
        "kiloPass.getState",
        "user.getAutoTopUpPaymentMethod",
    ]

    private static let optionalProcedures: Set<String> = [
        "user.getAutoTopUpPaymentMethod",
    ]

    private static let maxTopLevelEntries = procedures.count

    public static func fetchUsage(
        apiKey: String,
        scope: KiloUsageScope = .personal,
        environment: [String: String] = ProcessInfo.processInfo.environment) async throws -> KiloUsageSnapshot
    {
        guard !apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
            throw KiloUsageError.missingCredentials
        }

        let baseURL = KiloSettingsReader.apiURL(environment: environment)
        let request = try self.makeRequest(baseURL: baseURL, apiKey: apiKey, scope: scope)

        let response: ProviderHTTPResponse
        do {
            response = try await ProviderHTTPClient.shared.response(for: request)
        } catch {
            throw KiloUsageError.networkError(error.localizedDescription)
        }

        if let mapped = self.statusError(for: response.statusCode) {
            throw mapped
        }

        guard response.statusCode == 200 else {
            throw KiloUsageError.apiError(response.statusCode)
        }

        return try self.parseSnapshot(data: response.data)
    }

    static func _buildBatchURLForTesting(baseURL: URL) throws -> URL {
        try self.makeBatchURL(baseURL: baseURL)
    }

    static func _buildRequestForTesting(
        baseURL: URL,
        apiKey: String,
        scope: KiloUsageScope) throws -> URLRequest
    {
        try self.makeRequest(baseURL: baseURL, apiKey: apiKey, scope: scope)
    }

    private static func makeRequest(
        baseURL: URL,
        apiKey: String,
        scope: KiloUsageScope) throws -> URLRequest
    {
        let batchURL = try self.makeBatchURL(baseURL: baseURL)
        var request = URLRequest(url: batchURL)
        request.httpMethod = "GET"
        request.timeoutInterval = 15
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        if let orgId = scope.organizationID {
            request.setValue(orgId, forHTTPHeaderField: "X-KILOCODE-ORGANIZATIONID")
        }
        return request
    }

    public static func fetchOrganizations(
        apiKey: String,
        environment: [String: String] = ProcessInfo.processInfo.environment) async throws -> [KiloOrganization]
    {
        guard !apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
            throw KiloUsageError.missingCredentials
        }

        let baseURL = KiloSettingsReader.apiURL(environment: environment)
        let trpcRequest = try self.makeOrgListTRPCRequest(baseURL: baseURL, apiKey: apiKey)

        do {
            let response = try await ProviderHTTPClient.shared.response(for: trpcRequest)
            if response.statusCode == 404 {
                return try await self.fetchOrganizationsRESTFallback(apiKey: apiKey)
            }
            if let mapped = self.statusError(for: response.statusCode) {
                throw mapped
            }
            return try self.parseOrganizations(data: response.data)
        } catch let error as KiloUsageError {
            throw error
        } catch {
            throw KiloUsageError.networkError(error.localizedDescription)
        }
    }

    static func _parseOrganizationsForTesting(_ data: Data) throws -> [KiloOrganization] {
        try self.parseOrganizations(data: data)
    }

    private static func makeOrgListTRPCRequest(
        baseURL: URL,
        apiKey: String) throws -> URLRequest
    {
        let endpoint = baseURL.appendingPathComponent("user.getOrganizations")
        let inputData = try JSONSerialization.data(
            withJSONObject: ["0": ["json": NSNull()]] as [String: Any])
        guard let inputString = String(data: inputData, encoding: .utf8),
              var components = URLComponents(url: endpoint, resolvingAgainstBaseURL: false)
        else {
            throw KiloUsageError.parseFailed("Invalid org list endpoint")
        }
        components.queryItems = [
            URLQueryItem(name: "batch", value: "1"),
            URLQueryItem(name: "input", value: inputString),
        ]
        guard let url = components.url else {
            throw KiloUsageError.parseFailed("Invalid org list endpoint")
        }
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.timeoutInterval = 15
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        return request
    }

    private static func fetchOrganizationsRESTFallback(apiKey: String) async throws -> [KiloOrganization] {
        guard let url = URL(string: "https://api.kilo.ai/api/profile") else {
            throw KiloUsageError.parseFailed("Invalid REST fallback URL")
        }
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.timeoutInterval = 15
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Accept")

        let response = try await ProviderHTTPClient.shared.response(for: request)
        if let mapped = self.statusError(for: response.statusCode) {
            throw mapped
        }
        guard response.statusCode == 200 else {
            throw KiloUsageError.apiError(response.statusCode)
        }
        return try self.parseOrganizations(data: response.data)
    }

    private static func parseOrganizations(data: Data) throws -> [KiloOrganization] {
        guard let root = try? JSONSerialization.jsonObject(with: data) else {
            throw KiloUsageError.parseFailed("Invalid JSON")
        }

        // tRPC batch shape: [ { result: { data: { json: [orgs] } } } ]
        if let entries = root as? [[String: Any]],
           let first = entries.first,
           let resultObject = first["result"] as? [String: Any]
        {
            if let dataObject = resultObject["data"] as? [String: Any],
               let payload = dataObject["json"] as? [[String: Any]]
            {
                return self.decodeOrganizations(payload)
            }
            if let payload = resultObject["data"] as? [[String: Any]] {
                return self.decodeOrganizations(payload)
            }
        }

        // REST profile shape: { user: ..., organizations: [orgs] }
        if let dictionary = root as? [String: Any] {
            if let orgs = dictionary["organizations"] as? [[String: Any]] {
                return self.decodeOrganizations(orgs)
            }
            // Some single-procedure tRPC shapes flatten to { result: { data: { json: { organizations: [...] }}}}
            if let resultObject = dictionary["result"] as? [String: Any],
               let dataObject = resultObject["data"] as? [String: Any]
            {
                if let payload = dataObject["json"] as? [[String: Any]] {
                    return self.decodeOrganizations(payload)
                }
                if let payload = dataObject["json"] as? [String: Any],
                   let orgs = payload["organizations"] as? [[String: Any]]
                {
                    return self.decodeOrganizations(orgs)
                }
            }
        }

        return []
    }

    private static func decodeOrganizations(_ raw: [[String: Any]]) -> [KiloOrganization] {
        raw.compactMap { item -> KiloOrganization? in
            guard let id = item["id"] as? String, !id.isEmpty else { return nil }
            let name = (item["name"] as? String).map {
                $0.trimmingCharacters(in: .whitespacesAndNewlines)
            } ?? id
            let role = (item["role"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
            let normalizedRole = (role?.isEmpty ?? true) ? nil : role
            return KiloOrganization(id: id, name: name.isEmpty ? id : name, role: normalizedRole)
        }
    }

    static func _parseSnapshotForTesting(_ data: Data) throws -> KiloUsageSnapshot {
        try self.parseSnapshot(data: data)
    }

    static func _statusErrorForTesting(_ statusCode: Int) -> KiloUsageError? {
        self.statusError(for: statusCode)
    }

    private static func statusError(for statusCode: Int) -> KiloUsageError? {
        switch statusCode {
        case 401, 403:
            .unauthorized
        case 404:
            .endpointNotFound
        case 500...599:
            .serviceUnavailable(statusCode)
        default:
            nil
        }
    }

    private static func makeBatchURL(baseURL: URL) throws -> URL {
        let joinedProcedures = self.procedures.joined(separator: ",")
        let endpoint = baseURL.appendingPathComponent(joinedProcedures)

        let inputMap = Dictionary(uniqueKeysWithValues: self.procedures.indices.map {
            (String($0), ["json": NSNull()])
        })
        let inputData = try JSONSerialization.data(withJSONObject: inputMap)
        guard let inputString = String(data: inputData, encoding: .utf8) else {
            throw KiloUsageError.parseFailed("Invalid batch input")
        }

        guard var components = URLComponents(url: endpoint, resolvingAgainstBaseURL: false) else {
            throw KiloUsageError.parseFailed("Invalid batch endpoint")
        }
        components.queryItems = [
            URLQueryItem(name: "batch", value: "1"),
            URLQueryItem(name: "input", value: inputString),
        ]

        guard let url = components.url else {
            throw KiloUsageError.parseFailed("Invalid batch endpoint")
        }
        return url
    }

    private static func parseSnapshot(data: Data) throws -> KiloUsageSnapshot {
        guard let root = try? JSONSerialization.jsonObject(with: data) else {
            throw KiloUsageError.parseFailed("Invalid JSON")
        }

        let entriesByIndex = try self.responseEntriesByIndex(from: root)
        var payloadsByProcedure: [String: Any] = [:]

        for (index, procedure) in self.procedures.enumerated() {
            guard let entry = entriesByIndex[index] else { continue }
            if let mappedError = self.trpcError(from: entry) {
                guard self.isRequiredProcedure(procedure) else {
                    continue
                }
                throw mappedError
            }
            if let payload = self.resultPayload(from: entry) {
                payloadsByProcedure[procedure] = payload
            }
        }

        let creditFields = self.creditFields(from: payloadsByProcedure[self.procedures[0]])
        let passFields = self.passFields(from: payloadsByProcedure[self.procedures[1]])
        let planName = self.planName(from: payloadsByProcedure[self.procedures[1]])
        let autoTopUp = self.autoTopUpState(
            creditBlocksPayload: payloadsByProcedure[self.procedures[0]],
            autoTopUpPayload: payloadsByProcedure[self.procedures[2]])

        return KiloUsageSnapshot(
            creditsUsed: creditFields.used,
            creditsTotal: creditFields.total,
            creditsRemaining: creditFields.remaining,
            passUsed: passFields.used,
            passTotal: passFields.total,
            passRemaining: passFields.remaining,
            passBonus: passFields.bonus,
            passResetsAt: passFields.resetsAt,
            planName: planName,
            autoTopUpEnabled: autoTopUp.enabled,
            autoTopUpMethod: autoTopUp.method,
            updatedAt: Date())
    }

    private static func isRequiredProcedure(_ procedure: String) -> Bool {
        !self.optionalProcedures.contains(procedure)
    }

    private static func responseEntriesByIndex(from root: Any) throws -> [Int: [String: Any]] {
        if let entries = root as? [[String: Any]] {
            let limited = Array(entries.prefix(self.maxTopLevelEntries))
            return Dictionary(uniqueKeysWithValues: limited.enumerated().map { ($0.offset, $0.element) })
        }

        if let dictionary = root as? [String: Any] {
            if dictionary["result"] != nil || dictionary["error"] != nil {
                return [0: dictionary]
            }

            let indexedEntries = dictionary
                .compactMap { key, value -> (Int, [String: Any])? in
                    guard let index = Int(key),
                          let entry = value as? [String: Any]
                    else {
                        return nil
                    }
                    return (index, entry)
                }
            if !indexedEntries.isEmpty {
                let limitedEntries = indexedEntries.filter { $0.0 >= 0 && $0.0 < self.maxTopLevelEntries }
                return Dictionary(uniqueKeysWithValues: limitedEntries)
            }
        }

        throw KiloUsageError.parseFailed("Unexpected tRPC batch shape")
    }

    private static func trpcError(from entry: [String: Any]) -> KiloUsageError? {
        guard let errorObject = entry["error"] as? [String: Any] else { return nil }

        let code = self.stringValue(for: ["json", "data", "code"], in: errorObject)
            ?? self.stringValue(for: ["data", "code"], in: errorObject)
            ?? self.stringValue(for: ["code"], in: errorObject)
        let message = self.stringValue(for: ["json", "message"], in: errorObject)
            ?? self.stringValue(for: ["message"], in: errorObject)

        let combined = [code, message]
            .compactMap { $0?.lowercased() }
            .joined(separator: " ")

        if combined.contains("unauthorized") || combined.contains("forbidden") {
            return .unauthorized
        }

        if combined.contains("not_found") || combined.contains("not found") {
            return .endpointNotFound
        }

        return .parseFailed("tRPC error payload")
    }

    private static func resultPayload(from entry: [String: Any]) -> Any? {
        guard let resultObject = entry["result"] as? [String: Any] else { return nil }

        if let dataObject = resultObject["data"] as? [String: Any] {
            if let jsonPayload = dataObject["json"] {
                if jsonPayload is NSNull { return nil }
                return jsonPayload
            }
            return dataObject
        }

        if let jsonPayload = resultObject["json"] {
            if jsonPayload is NSNull { return nil }
            return jsonPayload
        }

        return nil
    }

    private static func creditFields(from payload: Any?) -> (used: Double?, total: Double?, remaining: Double?) {
        guard let payload else { return (nil, nil, nil) }

        let contexts = self.dictionaryContexts(from: payload)
        let blocks = self.firstArray(forKeys: ["creditBlocks"], in: contexts)

        if let blocks {
            var totalFromBlocks: Double = 0
            var remainingFromBlocks: Double = 0
            var sawTotal = false
            var sawRemaining = false

            for case let block as [String: Any] in blocks {
                if let amountMicroUSD = self.double(from: block["amount_mUsd"]) {
                    totalFromBlocks += amountMicroUSD / 1_000_000
                    sawTotal = true
                }
                if let balanceMicroUSD = self.double(from: block["balance_mUsd"]) {
                    remainingFromBlocks += balanceMicroUSD / 1_000_000
                    sawRemaining = true
                }
            }

            if sawTotal || sawRemaining {
                let total = sawTotal ? max(0, totalFromBlocks) : nil
                let remaining = sawRemaining ? max(0, remainingFromBlocks) : nil
                let used: Double? = if let total, let remaining {
                    max(0, total - remaining)
                } else {
                    nil
                }
                return (used, total, remaining)
            }
        }

        let genericBlocks = self.firstArray(forKeys: ["blocks"], in: contexts)
        let blockContexts = (genericBlocks ?? []).compactMap { $0 as? [String: Any] }

        var used = self.firstDouble(
            forKeys: ["used", "usedCredits", "consumed", "spent", "creditsUsed"],
            in: blockContexts)
        var total = self.firstDouble(forKeys: ["total", "totalCredits", "creditsTotal", "limit"], in: blockContexts)
        var remaining = self.firstDouble(
            forKeys: ["remaining", "remainingCredits", "creditsRemaining"],
            in: blockContexts)

        if used == nil {
            used = self.firstDouble(
                forKeys: ["used", "usedCredits", "creditsUsed", "consumed", "spent"],
                in: contexts)
        }
        if total == nil {
            total = self.firstDouble(forKeys: ["total", "totalCredits", "creditsTotal", "limit"], in: contexts)
        }
        if remaining == nil {
            remaining = self.firstDouble(
                forKeys: ["remaining", "remainingCredits", "creditsRemaining"],
                in: contexts)
        }

        if total == nil,
           let used,
           let remaining
        {
            total = used + remaining
        }

        if used == nil, total == nil, remaining == nil,
           let balanceMilliUSD = self.firstDouble(forKeys: ["totalBalance_mUsd"], in: contexts),
           balanceMilliUSD == 0
        {
            // Kilo may return an empty creditBlocks list for zero-balance accounts.
            // Keep this visible as an explicit exhausted edge state instead of "no data".
            return (0, 0, 0)
        }

        if used == nil,
           total == nil,
           remaining == nil,
           let balanceMilliUSD = self.firstDouble(forKeys: ["totalBalance_mUsd"], in: contexts)
        {
            let balance = max(0, balanceMilliUSD / 1_000_000)
            return (max(0, 0), balance, balance)
        }

        return (used, total, remaining)
    }

    private static func passFields(from payload: Any?) -> KiloPassFields {
        if let subscription = self.subscriptionData(from: payload) {
            let used = self.double(from: subscription["currentPeriodUsageUsd"]).map { max(0, $0) }
            let baseCredits = self.double(from: subscription["currentPeriodBaseCreditsUsd"]).map { max(0, $0) }
            let bonusCredits = max(0, self.double(from: subscription["currentPeriodBonusCreditsUsd"]) ?? 0)
            let total = baseCredits.map { $0 + bonusCredits }
            let remaining: Double? = if let total, let used {
                max(0, total - used)
            } else {
                nil
            }
            let resetsAt = self.date(from: subscription["nextBillingAt"])
                ?? self.date(from: subscription["nextRenewalAt"])
                ?? self.date(from: subscription["renewsAt"])
                ?? self.date(from: subscription["renewAt"])

            return KiloPassFields(
                used: used,
                total: total,
                remaining: remaining,
                bonus: bonusCredits > 0 ? bonusCredits : nil,
                resetsAt: resetsAt)
        }

        return self.fallbackPassFields(from: payload)
    }

    private static func fallbackPassFields(from payload: Any?) -> KiloPassFields {
        let contexts = self.dictionaryContexts(from: payload)
        guard !contexts.isEmpty else {
            return KiloPassFields(used: nil, total: nil, remaining: nil, bonus: nil, resetsAt: nil)
        }

        var total = self.moneyAmount(
            centsKeys: [
                "amountCents",
                "totalCents",
                "planAmountCents",
                "monthlyAmountCents",
                "limitCents",
                "includedCents",
                "valueCents",
            ],
            milliUSDKeys: [
                "amount_mUsd",
                "total_mUsd",
                "planAmount_mUsd",
                "limit_mUsd",
                "included_mUsd",
                "value_mUsd",
            ],
            plainKeys: [
                "amount",
                "total",
                "limit",
                "included",
                "value",
                "creditsTotal",
                "totalCredits",
                "planAmount",
            ],
            in: contexts)
        var used = self.moneyAmount(
            centsKeys: [
                "usedCents",
                "spentCents",
                "consumedCents",
                "usedAmountCents",
                "consumedAmountCents",
            ],
            milliUSDKeys: [
                "used_mUsd",
                "spent_mUsd",
                "consumed_mUsd",
                "usedAmount_mUsd",
            ],
            plainKeys: [
                "used",
                "spent",
                "consumed",
                "usage",
                "creditsUsed",
                "usedAmount",
                "consumedAmount",
            ],
            in: contexts)
        var remaining = self.moneyAmount(
            centsKeys: [
                "remainingCents",
                "remainingAmountCents",
                "availableCents",
                "leftCents",
                "balanceCents",
            ],
            milliUSDKeys: [
                "remaining_mUsd",
                "available_mUsd",
                "left_mUsd",
                "balance_mUsd",
            ],
            plainKeys: [
                "remaining",
                "available",
                "left",
                "balance",
                "creditsRemaining",
                "remainingAmount",
                "availableAmount",
            ],
            in: contexts)
        let bonus = self.moneyAmount(
            centsKeys: [
                "bonusCents",
                "bonusAmountCents",
                "includedBonusCents",
                "bonusRemainingCents",
            ],
            milliUSDKeys: [
                "bonus_mUsd",
                "bonusAmount_mUsd",
            ],
            plainKeys: [
                "bonus",
                "bonusAmount",
                "bonusCredits",
                "includedBonus",
            ],
            in: contexts)
        let resetsAt = self.firstDate(
            forKeys: [
                "resetAt",
                "resetsAt",
                "nextResetAt",
                "renewAt",
                "renewsAt",
                "nextRenewalAt",
                "currentPeriodEnd",
                "periodEndsAt",
                "expiresAt",
                "expiryAt",
            ],
            in: contexts)

        if total == nil,
           let used,
           let remaining
        {
            total = used + remaining
        }
        if used == nil,
           let total,
           let remaining
        {
            used = max(0, total - remaining)
        }
        if remaining == nil,
           let total,
           let used
        {
            remaining = max(0, total - used)
        }

        return KiloPassFields(
            used: used,
            total: total,
            remaining: remaining,
            bonus: bonus,
            resetsAt: resetsAt)
    }

    private static func planName(from payload: Any?) -> String? {
        if let subscription = self.subscriptionData(from: payload) {
            if let tier = self.string(from: subscription["tier"]) {
                let trimmed = tier.trimmingCharacters(in: .whitespacesAndNewlines)
                if !trimmed.isEmpty {
                    return self.planNameForTier(trimmed)
                }
            }
            return "Kilo Pass"
        }

        let contexts = self.dictionaryContexts(from: payload)
        let candidates = [
            self.firstString(
                forKeys: ["planName", "tier", "tierName", "passName", "subscriptionName"],
                in: contexts),
            self.stringValue(for: ["plan", "name"], in: contexts),
            self.stringValue(for: ["subscription", "plan", "name"], in: contexts),
            self.stringValue(for: ["subscription", "name"], in: contexts),
            self.stringValue(for: ["pass", "name"], in: contexts),
            self.stringValue(for: ["state", "name"], in: contexts),
            self.stringValue(for: ["state"], in: contexts),
        ]

        for candidate in candidates {
            guard let trimmed = candidate?.trimmingCharacters(in: .whitespacesAndNewlines), !trimmed.isEmpty else {
                continue
            }
            return trimmed
        }
        if let fallback = self.firstString(forKeys: ["name"], in: contexts),
           fallback.lowercased().contains("pass")
        {
            return fallback
        }
        return nil
    }

    private static func autoTopUpState(
        creditBlocksPayload: Any?,
        autoTopUpPayload: Any?) -> (enabled: Bool?, method: String?)
    {
        let creditContexts = self.dictionaryContexts(from: creditBlocksPayload)
        let autoTopUpContexts = self.dictionaryContexts(from: autoTopUpPayload)
        let enabled = self.firstBool(forKeys: ["enabled", "isEnabled", "active"], in: autoTopUpContexts)
            ?? self.boolFromStatusString(self.firstString(forKeys: ["status"], in: autoTopUpContexts))
            ?? self.firstBool(forKeys: ["autoTopUpEnabled"], in: creditContexts)

        let rawMethod = self.firstString(
            forKeys: ["paymentMethod", "paymentMethodType", "method", "cardBrand"],
            in: autoTopUpContexts)?.trimmingCharacters(in: .whitespacesAndNewlines)
        let amount = self.moneyAmount(
            centsKeys: ["amountCents"],
            milliUSDKeys: [],
            plainKeys: ["amount", "topUpAmount", "amountUsd"],
            in: autoTopUpContexts)

        let method: String? = if let rawMethod, !rawMethod.isEmpty {
            rawMethod
        } else if let amount, amount > 0 {
            self.currencyAmountLabel(amount)
        } else {
            nil
        }

        return (enabled, method)
    }

    private static func subscriptionData(from payload: Any?) -> [String: Any]? {
        guard let payloadDictionary = payload as? [String: Any] else {
            return nil
        }

        if let subscription = payloadDictionary["subscription"] as? [String: Any] {
            return subscription
        }

        if payloadDictionary["subscription"] is NSNull {
            return nil
        }

        let hasSubscriptionShape = payloadDictionary["currentPeriodUsageUsd"] != nil ||
            payloadDictionary["currentPeriodBaseCreditsUsd"] != nil ||
            payloadDictionary["currentPeriodBonusCreditsUsd"] != nil ||
            payloadDictionary["tier"] != nil
        return hasSubscriptionShape ? payloadDictionary : nil
    }

    private static func planNameForTier(_ tier: String) -> String {
        switch tier {
        case "tier_19":
            "Starter"
        case "tier_49":
            "Pro"
        case "tier_199":
            "Expert"
        default:
            tier
        }
    }

    private static func string(from raw: Any?) -> String? {
        if let value = raw as? String {
            return value
        }
        return nil
    }

    private static func dictionaryContexts(from payload: Any?) -> [[String: Any]] {
        guard let payload else { return [] }
        guard let dictionary = payload as? [String: Any] else { return [] }

        var contexts: [[String: Any]] = []
        var queue: [([String: Any], Int)] = [(dictionary, 0)]
        let maxDepth = 2

        while !queue.isEmpty {
            let (current, depth) = queue.removeFirst()
            contexts.append(current)

            guard depth < maxDepth else {
                continue
            }

            for value in current.values {
                if let nested = value as? [String: Any] {
                    queue.append((nested, depth + 1))
                    continue
                }
                if let nestedArray = value as? [Any] {
                    for case let nested as [String: Any] in nestedArray {
                        queue.append((nested, depth + 1))
                    }
                }
            }
        }

        return contexts
    }

    private static func firstArray(forKeys keys: [String], in contexts: [[String: Any]]) -> [Any]? {
        for context in contexts {
            for key in keys {
                if let values = context[key] as? [Any] {
                    return values
                }
            }
        }
        return nil
    }

    private static func firstDouble(forKeys keys: [String], in contexts: [[String: Any]]) -> Double? {
        for context in contexts {
            for key in keys {
                if let value = self.double(from: context[key]) {
                    return value
                }
            }
        }
        return nil
    }

    private static func firstString(forKeys keys: [String], in contexts: [[String: Any]]) -> String? {
        for context in contexts {
            for key in keys {
                if let value = context[key] as? String {
                    return value
                }
            }
        }
        return nil
    }

    private static func firstBool(forKeys keys: [String], in contexts: [[String: Any]]) -> Bool? {
        for context in contexts {
            for key in keys {
                if let value = self.bool(from: context[key]) {
                    return value
                }
            }
        }
        return nil
    }

    private static func firstDate(forKeys keys: [String], in contexts: [[String: Any]]) -> Date? {
        for context in contexts {
            for key in keys {
                if let value = self.date(from: context[key]) {
                    return value
                }
            }
        }
        return nil
    }

    private static func moneyAmount(
        centsKeys: [String],
        milliUSDKeys: [String],
        plainKeys: [String],
        in contexts: [[String: Any]]) -> Double?
    {
        if let cents = self.firstDouble(forKeys: centsKeys, in: contexts) {
            return cents / 100
        }
        if let milliUSD = self.firstDouble(forKeys: milliUSDKeys, in: contexts) {
            return milliUSD / 1_000_000
        }
        return self.firstDouble(forKeys: plainKeys, in: contexts)
    }

    private static func currencyAmountLabel(_ amount: Double) -> String {
        if amount.rounded(.towardZero) == amount {
            return String(format: "$%.0f", amount)
        }
        return String(format: "$%.2f", amount)
    }

    private static func stringValue(for path: [String], in dictionary: [String: Any]) -> String? {
        var cursor: Any = dictionary
        for key in path {
            guard let next = (cursor as? [String: Any])?[key] else {
                return nil
            }
            cursor = next
        }
        return cursor as? String
    }

    private static func stringValue(for path: [String], in contexts: [[String: Any]]) -> String? {
        for context in contexts {
            if let value = self.stringValue(for: path, in: context) {
                return value
            }
        }
        return nil
    }

    private static func boolFromStatusString(_ status: String?) -> Bool? {
        guard let status = status?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(),
              !status.isEmpty
        else {
            return nil
        }

        switch status {
        case "enabled", "active", "on":
            return true
        case "disabled", "inactive", "off", "none":
            return false
        default:
            return nil
        }
    }

    private static func double(from raw: Any?) -> Double? {
        switch raw {
        case let value as Double:
            value
        case let value as Int:
            Double(value)
        case let value as NSNumber:
            value.doubleValue
        case let value as String:
            Double(value.trimmingCharacters(in: .whitespacesAndNewlines))
        default:
            nil
        }
    }

    private static func date(from raw: Any?) -> Date? {
        switch raw {
        case let value as Date:
            return value
        case let value as Double:
            return self.dateFromEpoch(value)
        case let value as Int:
            return self.dateFromEpoch(Double(value))
        case let value as NSNumber:
            return self.dateFromEpoch(value.doubleValue)
        case let value as String:
            let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
            guard !trimmed.isEmpty else { return nil }
            if let numeric = Double(trimmed) {
                return self.dateFromEpoch(numeric)
            }

            let withFractional = ISO8601DateFormatter()
            withFractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
            if let parsed = withFractional.date(from: trimmed) {
                return parsed
            }

            let plain = ISO8601DateFormatter()
            plain.formatOptions = [.withInternetDateTime]
            return plain.date(from: trimmed)
        default:
            return nil
        }
    }

    private static func dateFromEpoch(_ value: Double) -> Date {
        let seconds = abs(value) > 10_000_000_000 ? value / 1000 : value
        return Date(timeIntervalSince1970: seconds)
    }

    private static func bool(from raw: Any?) -> Bool? {
        switch raw {
        case let value as Bool:
            return value
        case let value as NSNumber:
            return value.boolValue
        case let value as String:
            let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
            if ["true", "1", "yes", "enabled", "on"].contains(normalized) {
                return true
            }
            if ["false", "0", "no", "disabled", "off"].contains(normalized) {
                return false
            }
            return nil
        default:
            return nil
        }
    }
}
