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

public struct MiniMaxUsageFetcher: Sendable {
    static let log = CodexBarLog.logger(LogCategories.provider(.minimax, scope: "usage"))
    private static let codingPlanPath = "user-center/payment/coding-plan"
    private static let codingPlanQuery = "cycle_type=3"
    private static let codingPlanRemainsPath = "v1/api/openplatform/coding_plan/remains"
    private static let tokenPlanRemainsPath = "v1/token_plan/remains"
    private static let billingHistoryPath = "account/amount"
    private static let billingHistoryLimit = 100
    private struct RemainsContext {
        let authorizationToken: String?
        let groupID: String?
    }

    struct WebFetchContext {
        let cookie: String
        let authorizationToken: String?
        let region: MiniMaxAPIRegion
        let environment: [String: String]
        let transport: any ProviderHTTPTransport
    }

    public static func fetchUsage(
        cookieHeader: String,
        authorizationToken: String? = nil,
        groupID: String? = nil,
        region: MiniMaxAPIRegion = .global,
        environment: [String: String] = ProcessInfo.processInfo.environment,
        includeBillingHistory: Bool = true,
        session transport: any ProviderHTTPTransport = ProviderHTTPClient.shared,
        now: Date = Date()) async throws -> MiniMaxUsageSnapshot
    {
        guard let cookie = MiniMaxCookieHeader.normalized(from: cookieHeader) else {
            throw MiniMaxUsageError.invalidCredentials
        }
        if let rejectedKey = MiniMaxSettingsReader.rejectedEndpointOverrideKey(environment: environment) {
            throw ProviderEndpointOverrideError.minimax(rejectedKey)
        }

        let context = WebFetchContext(
            cookie: cookie,
            authorizationToken: authorizationToken,
            region: region,
            environment: environment,
            transport: transport)
        do {
            let htmlSnapshot = try await self.fetchCodingPlanHTML(context: context, now: now)
            if htmlSnapshot.services?.isEmpty != false {
                do {
                    Self.log.debug("MiniMax coding plan HTML lacks service quota data, trying remains API")
                    let remainsSnapshot = try await self.fetchCodingPlanRemains(
                        context: context,
                        remainsContext: RemainsContext(
                            authorizationToken: authorizationToken,
                            groupID: groupID),
                        now: now)
                        .withPlanNameIfMissing(htmlSnapshot.planName)
                    let snapshot = try await self.attachingSubscriptionMetadataIfAvailable(
                        to: remainsSnapshot,
                        context: context,
                        groupID: groupID)
                    return try await self.attachingBillingIfAvailable(
                        to: snapshot,
                        context: context,
                        includeBillingHistory: includeBillingHistory,
                        now: now)
                } catch {
                    if self.shouldRethrowAfterHTMLFallback(error) {
                        throw error
                    }
                    Self.log.debug(
                        "MiniMax remains API enrichment failed after HTML fallback: \(error.localizedDescription)")
                }
            }
            let snapshot = try await self.attachingSubscriptionMetadataIfAvailable(
                to: htmlSnapshot,
                context: context,
                groupID: groupID)
            return try await self.attachingBillingIfAvailable(
                to: snapshot,
                context: context,
                includeBillingHistory: includeBillingHistory,
                now: now)
        } catch let error as MiniMaxUsageError {
            if case .parseFailed = error {
                Self.log.debug("MiniMax coding plan HTML parse failed, trying remains API")
                let snapshot = try await self.attachingSubscriptionMetadataIfAvailable(
                    to: self.fetchCodingPlanRemains(
                        context: context,
                        remainsContext: RemainsContext(
                            authorizationToken: authorizationToken,
                            groupID: groupID),
                        now: now),
                    context: context,
                    groupID: groupID)
                return try await self.attachingBillingIfAvailable(
                    to: snapshot,
                    context: context,
                    includeBillingHistory: includeBillingHistory,
                    now: now)
            }
            throw error
        }
    }

    public static func fetchUsage(
        apiToken: String,
        region: MiniMaxAPIRegion = .global,
        now: Date = Date(),
        session transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> MiniMaxUsageSnapshot
    {
        let cleaned = apiToken.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !cleaned.isEmpty else {
            throw MiniMaxUsageError.invalidCredentials
        }

        // Historically, MiniMax API token fetching used a China endpoint by default in some configurations. If the
        // user has no persisted region and we default to `.global`, retry the China endpoint when the global host
        // rejects the token so upgrades don't regress existing setups.
        if region != .global {
            return try await self.fetchUsageOnce(apiToken: cleaned, region: region, now: now, transport: transport)
        }

        do {
            return try await self.fetchUsageOnce(
                apiToken: cleaned,
                region: .global,
                now: now,
                transport: transport)
        } catch let error as MiniMaxUsageError {
            guard case .invalidCredentials = error else { throw error }
            Self.log.debug("MiniMax API token rejected for global host, retrying China mainland host")
            do {
                return try await self.fetchUsageOnce(
                    apiToken: cleaned,
                    region: .chinaMainland,
                    now: now,
                    transport: transport)
            } catch {
                // Preserve the original invalid-credentials error so the fetch pipeline can fall back to web.
                Self.log.debug("MiniMax China mainland retry failed, preserving global invalidCredentials")
                throw MiniMaxUsageError.invalidCredentials
            }
        }
    }

    private static func fetchUsageOnce(
        apiToken: String,
        region: MiniMaxAPIRegion,
        now: Date,
        transport: any ProviderHTTPTransport) async throws -> MiniMaxUsageSnapshot
    {
        var lastError: Error?
        var tokenPlanCredentialFailure = false
        for remainsURL in [region.tokenPlanRemainsURL, region.apiRemainsURL] {
            do {
                return try await self.fetchAPIUsageOnce(
                    apiToken: apiToken,
                    remainsURL: remainsURL,
                    now: now,
                    transport: transport)
            } catch let error as MiniMaxUsageError {
                lastError = error
                guard remainsURL == region.tokenPlanRemainsURL,
                      self.shouldTryLegacyAPIEndpoint(after: error)
                else {
                    if tokenPlanCredentialFailure {
                        throw MiniMaxUsageError.invalidCredentials
                    }
                    throw error
                }
                if case .invalidCredentials = error {
                    tokenPlanCredentialFailure = true
                }
                Self.log.debug("MiniMax token-plan API failed, trying legacy coding-plan endpoint")
            }
        }
        if let lastError { throw lastError }
        throw MiniMaxUsageError.parseFailed("Missing MiniMax API remains URL.")
    }

    private static func fetchAPIUsageOnce(
        apiToken: String,
        remainsURL: URL,
        now: Date,
        transport: any ProviderHTTPTransport) async throws -> MiniMaxUsageSnapshot
    {
        var request = URLRequest(url: remainsURL)
        request.httpMethod = "GET"
        request.setValue("Bearer \(apiToken)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "accept")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("CodexBar", forHTTPHeaderField: "MM-API-Source")

        let response: ProviderHTTPResponse
        do {
            response = try await transport.response(for: request)
        } catch {
            throw self.normalizedTransportError(error)
        }

        guard response.statusCode == 200 else {
            let body = String(data: response.data, encoding: .utf8) ?? ""
            Self.log.error("MiniMax returned \(response.statusCode): \(body)")
            if response.statusCode == 401 || response.statusCode == 403 {
                throw MiniMaxUsageError.invalidCredentials
            }
            throw MiniMaxUsageError.apiError("HTTP \(response.statusCode)")
        }

        let snapshot: MiniMaxUsageSnapshot
        do {
            snapshot = try MiniMaxUsageParser.parseCodingPlanRemains(data: response.data, now: now)
        } catch let error as MiniMaxUsageError {
            throw self.normalizedAPITokenError(error)
        } catch {
            throw MiniMaxUsageError.parseFailed(error.localizedDescription)
        }
        if let services = snapshot.services, !services.isEmpty {
            Self.log.debug("MiniMax multi-service response detected: \(services.count) services")
        }
        return snapshot
    }

    private static func normalizedAPITokenError(_ error: MiniMaxUsageError) -> MiniMaxUsageError {
        guard case let .apiError(message) = error else { return error }
        let normalizedMessage = message.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
        return normalizedMessage == "invalid api key" ? .invalidCredentials : error
    }

    private static func shouldTryLegacyAPIEndpoint(after error: MiniMaxUsageError) -> Bool {
        switch error {
        case .invalidCredentials:
            true
        case let .apiError(message):
            message.contains("HTTP 404") || message.contains("HTTP 405")
        case .networkError, .parseFailed:
            true
        }
    }

    private static func fetchCodingPlanHTML(
        context: WebFetchContext,
        now: Date) async throws -> MiniMaxUsageSnapshot
    {
        let url = self.resolveCodingPlanURL(region: context.region, environment: context.environment)
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue(context.cookie, forHTTPHeaderField: "Cookie")
        if let authorizationToken = context.authorizationToken {
            request.setValue("Bearer \(authorizationToken)", forHTTPHeaderField: "Authorization")
        }
        let acceptHeader = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
        request.setValue(acceptHeader, forHTTPHeaderField: "accept")
        let userAgent =
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " +
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
        request.setValue(userAgent, forHTTPHeaderField: "user-agent")
        request.setValue("en-US,en;q=0.9", forHTTPHeaderField: "accept-language")
        let origin = self.originURL(from: url)
        request.setValue(origin.absoluteString, forHTTPHeaderField: "origin")
        request.setValue(
            self.resolveCodingPlanRefererURL(region: context.region, environment: context.environment).absoluteString,
            forHTTPHeaderField: "referer")

        let response: ProviderHTTPResponse
        do {
            response = try await context.transport.response(for: request)
        } catch {
            throw self.normalizedTransportError(error)
        }

        guard response.statusCode == 200 else {
            let body = String(data: response.data, encoding: .utf8) ?? ""
            Self.log.error("MiniMax returned \(response.statusCode): \(body)")
            if response.statusCode == 401 || response.statusCode == 403 {
                throw MiniMaxUsageError.invalidCredentials
            }
            throw MiniMaxUsageError.apiError("HTTP \(response.statusCode)")
        }

        if let contentType = response.response.value(forHTTPHeaderField: "Content-Type"),
           contentType.lowercased().contains("application/json")
        {
            let snapshot: MiniMaxUsageSnapshot
            do {
                snapshot = try MiniMaxUsageParser.parseCodingPlanRemains(data: response.data, now: now)
            } catch let error as MiniMaxUsageError {
                throw error
            } catch {
                throw MiniMaxUsageError.parseFailed(error.localizedDescription)
            }
            if let services = snapshot.services, !services.isEmpty {
                Self.log.debug("MiniMax multi-service response detected: \(services.count) services")
            }
            return snapshot
        }

        let html = String(data: response.data, encoding: .utf8) ?? ""
        if html.contains("__NEXT_DATA__") {
            Self.log.debug("MiniMax coding plan HTML contains __NEXT_DATA__")
        }
        if self.looksSignedOut(html: html) {
            throw MiniMaxUsageError.invalidCredentials
        }
        return try MiniMaxUsageParser.parse(html: html, now: now)
    }

    private static func fetchCodingPlanRemains(
        context: WebFetchContext,
        remainsContext: RemainsContext,
        now: Date) async throws -> MiniMaxUsageSnapshot
    {
        var lastError: Error?
        for baseRemainsURL in self.resolveRemainsURLs(region: context.region, environment: context.environment) {
            do {
                return try await self.fetchCodingPlanRemainsOnce(
                    baseRemainsURL: baseRemainsURL,
                    context: context,
                    remainsContext: remainsContext,
                    now: now)
            } catch let error as MiniMaxUsageError {
                lastError = error
                guard self.shouldTryNextRemainsURL(after: error) else { throw error }
                Self.log.debug("MiniMax remains API failed for \(baseRemainsURL.host ?? "unknown host"), trying next")
            }
        }
        if let lastError { throw lastError }
        throw MiniMaxUsageError.parseFailed("Missing MiniMax remains URL.")
    }

    private static func fetchCodingPlanRemainsOnce(
        baseRemainsURL: URL,
        context: WebFetchContext,
        remainsContext: RemainsContext,
        now: Date) async throws -> MiniMaxUsageSnapshot
    {
        let remainsURL = self.appendGroupID(remainsContext.groupID, to: baseRemainsURL)
        var request = URLRequest(url: remainsURL)
        request.httpMethod = "GET"
        request.setValue(context.cookie, forHTTPHeaderField: "Cookie")
        if let authorizationToken = remainsContext.authorizationToken {
            request.setValue("Bearer \(authorizationToken)", forHTTPHeaderField: "Authorization")
        }
        let acceptHeader = "application/json, text/plain, */*"
        request.setValue(acceptHeader, forHTTPHeaderField: "accept")
        request.setValue("XMLHttpRequest", forHTTPHeaderField: "x-requested-with")
        let userAgent =
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " +
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
        request.setValue(userAgent, forHTTPHeaderField: "user-agent")
        request.setValue("en-US,en;q=0.9", forHTTPHeaderField: "accept-language")
        let origin = self.originURL(from: baseRemainsURL)
        request.setValue(origin.absoluteString, forHTTPHeaderField: "origin")
        request.setValue(
            self.resolveCodingPlanRefererURL(region: context.region, environment: context.environment).absoluteString,
            forHTTPHeaderField: "referer")

        let response: ProviderHTTPResponse
        do {
            response = try await context.transport.response(for: request)
        } catch {
            throw self.normalizedTransportError(error)
        }

        guard response.statusCode == 200 else {
            let body = String(data: response.data, encoding: .utf8) ?? ""
            Self.log.error("MiniMax returned \(response.statusCode): \(body)")
            if response.statusCode == 401 || response.statusCode == 403 {
                throw MiniMaxUsageError.invalidCredentials
            }
            throw MiniMaxUsageError.apiError("HTTP \(response.statusCode)")
        }

        if let contentType = response.response.value(forHTTPHeaderField: "Content-Type"),
           contentType.lowercased().contains("application/json")
        {
            let snapshot: MiniMaxUsageSnapshot
            do {
                snapshot = try MiniMaxUsageParser.parseCodingPlanRemains(data: response.data, now: now)
            } catch let error as MiniMaxUsageError {
                throw error
            } catch {
                throw MiniMaxUsageError.parseFailed(error.localizedDescription)
            }
            if let services = snapshot.services, !services.isEmpty {
                Self.log.debug("MiniMax multi-service response detected: \(services.count) services")
            }
            return snapshot
        }

        let html = String(data: response.data, encoding: .utf8) ?? ""
        if self.looksSignedOut(html: html) {
            throw MiniMaxUsageError.invalidCredentials
        }
        return try MiniMaxUsageParser.parse(html: html, now: now)
    }

    private static func shouldTryNextRemainsURL(after error: MiniMaxUsageError) -> Bool {
        switch error {
        case .invalidCredentials:
            false
        case let .apiError(message):
            message.contains("HTTP 404") || message.contains("HTTP 405")
        case .networkError, .parseFailed:
            true
        }
    }

    private static func normalizedTransportError(_ error: Error) -> Error {
        if error is MiniMaxUsageError || error is CancellationError {
            return error
        }
        if let urlError = error as? URLError {
            if urlError.code == .cancelled {
                return error
            }
            if urlError.code == .badServerResponse {
                return MiniMaxUsageError.networkError("Invalid response")
            }
            return MiniMaxUsageError.networkError(urlError.localizedDescription)
        }
        return error
    }

    private static func shouldRethrowAfterHTMLFallback(_ error: Error) -> Bool {
        if error is CancellationError { return true }
        if let urlError = error as? URLError, urlError.code == .cancelled { return true }
        if let minimaxError = error as? MiniMaxUsageError {
            if case .invalidCredentials = minimaxError { return true }
        }
        if error is ProviderEndpointOverrideError { return true }
        return false
    }

    private static func attachingBillingIfAvailable(
        to snapshot: MiniMaxUsageSnapshot,
        context: WebFetchContext,
        includeBillingHistory: Bool,
        now: Date) async throws -> MiniMaxUsageSnapshot
    {
        guard includeBillingHistory else { return snapshot }
        do {
            let billing = try await self.fetchBillingSummary(context: context, now: now)
            return snapshot.withBillingSummary(billing)
        } catch is CancellationError {
            throw CancellationError()
        } catch let error as URLError where error.code == .cancelled {
            throw error
        } catch let error as MiniMaxUsageError {
            if case .invalidCredentials = error, context.authorizationToken != nil {
                throw error
            }
            Self.log.debug("MiniMax billing history unavailable: \(error.localizedDescription)")
            return snapshot
        } catch {
            Self.log.debug("MiniMax billing history unavailable: \(error.localizedDescription)")
            return snapshot
        }
    }

    private static func fetchBillingSummary(context: WebFetchContext, now: Date) async throws -> MiniMaxBillingSummary {
        var records: [MiniMaxBillingRecord] = []
        var totalCount: Int?

        var page = 1
        while true {
            let url = self.resolveBillingHistoryURL(
                region: context.region,
                environment: context.environment,
                page: page,
                limit: Self.billingHistoryLimit)
            let response = try await self.billingHistoryResponse(url: url, context: context)
            guard response.statusCode == 200 else {
                let body = String(data: response.data, encoding: .utf8) ?? ""
                Self.log.debug("MiniMax billing history returned \(response.statusCode): \(body)")
                if response.statusCode == 401 || response.statusCode == 403 {
                    throw MiniMaxUsageError.invalidCredentials
                }
                throw MiniMaxUsageError.apiError("HTTP \(response.statusCode)")
            }

            let payload = try MiniMaxBillingHistoryParser.decodePayload(data: response.data)
            if let status = payload.baseResp?.statusCode, status != 0 {
                let message = payload.baseResp?.statusMessage ?? "status_code \(status)"
                throw MiniMaxUsageError.apiError(message)
            }
            totalCount = payload.totalCount ?? totalCount
            guard !payload.chargeRecords.isEmpty else { break }
            records.append(contentsOf: payload.chargeRecords)
            if MiniMaxBillingHistoryParser.containsRecordBefore30DayWindow(payload.chargeRecords, now: now) { break }
            if let totalCount, records.count >= totalCount { break }
            page += 1
        }

        return MiniMaxBillingHistoryParser.aggregate(records: records, now: now)
    }

    private static func billingHistoryResponse(
        url: URL,
        context: WebFetchContext) async throws -> ProviderHTTPResponse
    {
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue(context.cookie, forHTTPHeaderField: "Cookie")
        if let authorizationToken = context.authorizationToken {
            request.setValue("Bearer \(authorizationToken)", forHTTPHeaderField: "Authorization")
        }
        request.setValue("application/json, text/plain, */*", forHTTPHeaderField: "accept")
        request.setValue("XMLHttpRequest", forHTTPHeaderField: "x-requested-with")
        let userAgent =
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " +
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
        request.setValue(userAgent, forHTTPHeaderField: "user-agent")
        request.setValue("en-US,en;q=0.9", forHTTPHeaderField: "accept-language")
        let origin = self.originURL(from: url)
        request.setValue(origin.absoluteString, forHTTPHeaderField: "origin")
        request.setValue(origin.appendingPathComponent("account").absoluteString, forHTTPHeaderField: "referer")

        do {
            return try await context.transport.response(for: request)
        } catch let error as URLError where error.code == .badServerResponse {
            throw MiniMaxUsageError.networkError("Invalid response")
        } catch {
            throw error
        }
    }

    private static func appendGroupID(_ groupID: String?, to url: URL) -> URL {
        guard let groupID, !groupID.isEmpty else { return url }
        guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return url }
        var queryItems = components.queryItems ?? []
        queryItems.append(URLQueryItem(name: "GroupId", value: groupID))
        components.queryItems = queryItems
        return components.url ?? url
    }

    static func originURL(from url: URL) -> URL {
        guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return url }
        components.path = ""
        components.query = nil
        components.fragment = nil
        return components.url ?? url
    }

    static func resolveCodingPlanURL(
        region: MiniMaxAPIRegion,
        environment: [String: String]) -> URL
    {
        if let override = MiniMaxSettingsReader.codingPlanURL(environment: environment) {
            return override
        }
        if let host = MiniMaxSettingsReader.hostOverride(environment: environment),
           let hostURL = self.url(from: host, path: Self.codingPlanPath, query: Self.codingPlanQuery)
        {
            return hostURL
        }
        return region.codingPlanURL
    }

    static func resolveCodingPlanRefererURL(
        region: MiniMaxAPIRegion,
        environment: [String: String]) -> URL
    {
        if let override = MiniMaxSettingsReader.codingPlanURL(environment: environment) {
            if var components = URLComponents(url: override, resolvingAgainstBaseURL: false) {
                components.query = nil
                return components.url ?? override
            }
            return override
        }
        if let host = MiniMaxSettingsReader.hostOverride(environment: environment),
           let hostURL = self.url(from: host, path: Self.codingPlanPath)
        {
            return hostURL
        }
        return region.codingPlanRefererURL
    }

    static func resolveRemainsURL(
        region: MiniMaxAPIRegion,
        environment: [String: String]) -> URL
    {
        if let override = MiniMaxSettingsReader.remainsURL(environment: environment) {
            return override
        }
        if let host = MiniMaxSettingsReader.hostOverride(environment: environment),
           let hostURL = self.url(from: host, path: Self.codingPlanRemainsPath)
        {
            return hostURL
        }
        return region.remainsURL
    }

    static func resolveRemainsURLs(
        region: MiniMaxAPIRegion,
        environment: [String: String]) -> [URL]
    {
        if let override = MiniMaxSettingsReader.remainsURL(environment: environment) {
            return [override]
        }
        if let host = MiniMaxSettingsReader.hostOverride(environment: environment),
           let hostURL = self.url(from: host, path: Self.codingPlanRemainsPath)
        {
            return [hostURL]
        }

        let primary = region.remainsURL
        let webCandidates = self.webRemainsFallbackURLs(region: region)
        return self.deduplicated([primary] + webCandidates)
    }

    static func resolveTokenPlanRemainsURL(region: MiniMaxAPIRegion) -> URL {
        region.tokenPlanRemainsURL
    }

    private static func webRemainsFallbackURLs(region: MiniMaxAPIRegion) -> [URL] {
        let hosts = switch region {
        case .global:
            ["https://www.minimax.io"]
        case .chinaMainland:
            ["https://www.minimaxi.com"]
        }
        return hosts.compactMap { self.url(from: $0, path: Self.codingPlanRemainsPath) }
    }

    private static func deduplicated(_ urls: [URL]) -> [URL] {
        var seen: Set<String> = []
        var result: [URL] = []
        for url in urls {
            let key = url.absoluteString
            guard !seen.contains(key) else { continue }
            seen.insert(key)
            result.append(url)
        }
        return result
    }

    static func resolveBillingHistoryURL(
        region: MiniMaxAPIRegion,
        environment: [String: String],
        page: Int,
        limit: Int = Self.billingHistoryLimit) -> URL
    {
        if let override = MiniMaxSettingsReader.billingHistoryURL(environment: environment) {
            return self.billingHistoryURL(from: override, page: page, limit: limit)
        }
        if let host = MiniMaxSettingsReader.hostOverride(environment: environment),
           let hostURL = self.url(from: host, path: Self.billingHistoryPath)
        {
            return self.billingHistoryURL(from: hostURL, page: page, limit: limit)
        }
        return region.billingHistoryURL(page: page, limit: limit)
    }

    private static func billingHistoryURL(from url: URL, page: Int, limit: Int) -> URL {
        guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return url }
        var items = components.queryItems?.filter {
            $0.name != "page" && $0.name != "limit" && $0.name != "aggregate"
        } ?? []
        items.append(URLQueryItem(name: "page", value: "\(page)"))
        items.append(URLQueryItem(name: "limit", value: "\(limit)"))
        items.append(URLQueryItem(name: "aggregate", value: "false"))
        components.queryItems = items
        return components.url ?? url
    }

    static func url(from raw: String, path: String? = nil, query: String? = nil) -> URL? {
        guard let cleaned = MiniMaxSettingsReader.cleaned(raw) else { return nil }

        func compose(_ base: URL) -> URL? {
            var components = URLComponents(url: base, resolvingAgainstBaseURL: false)!
            if let path { components.path = "/" + path }
            if let query { components.query = query }
            return components.url
        }

        guard let base = ProviderEndpointOverrideValidator.normalizedHTTPSURL(from: cleaned) else { return nil }
        return compose(base)
    }

    private static func logCodingPlanStatus(payload: MiniMaxCodingPlanPayload) {
        let baseResponse = payload.data.baseResp ?? payload.baseResp
        guard let status = baseResponse?.statusCode else { return }
        let message = baseResponse?.statusMessage ?? ""
        if !message.isEmpty {
            Self.log.debug("MiniMax coding plan status \(status): \(message)")
        } else {
            Self.log.debug("MiniMax coding plan status \(status)")
        }
    }

    private static func looksSignedOut(html: String) -> Bool {
        let lower = self.visibleText(from: html).lowercased()
        return lower.contains("sign in") || lower.contains("log in") || lower.contains("登录") || lower.contains("登入")
    }

    static func _looksSignedOutForTesting(html: String) -> Bool {
        self.looksSignedOut(html: html)
    }

    private static func visibleText(from html: String) -> String {
        let patterns = [
            #"(?is)<script\b[^>]*>.*?</script>"#,
            #"(?is)<style\b[^>]*>.*?</style>"#,
            #"(?is)<!--.*?-->"#,
            #"<[^>]+>"#,
            #"\s+"#,
        ]

        return patterns.enumerated().reduce(html) { result, item in
            let replacement = item.offset == patterns.count - 1 ? " " : ""
            return result.replacingOccurrences(
                of: item.element,
                with: replacement,
                options: .regularExpression)
        }
        .trimmingCharacters(in: .whitespacesAndNewlines)
    }
}

struct MiniMaxCodingPlanPayload: Decodable {
    let baseResp: MiniMaxBaseResponse?
    let data: MiniMaxCodingPlanData

    private enum CodingKeys: String, CodingKey {
        case baseResp = "base_resp"
        case data
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.baseResp = try container.decodeIfPresent(MiniMaxBaseResponse.self, forKey: .baseResp)
        if container.contains(.data) {
            let dataDecoder = try container.superDecoder(forKey: .data)
            self.data = try MiniMaxCodingPlanData(from: dataDecoder)
        } else {
            self.data = try MiniMaxCodingPlanData(from: decoder)
        }
    }
}

struct MiniMaxCodingPlanData: Decodable {
    let baseResp: MiniMaxBaseResponse?
    let currentSubscribeTitle: String?
    let planName: String?
    let comboTitle: String?
    let currentPlanTitle: String?
    let currentComboCard: MiniMaxComboCard?
    let pointsBalance: Double?
    let modelRemains: [MiniMaxModelRemains]

    private enum CodingKeys: String, CodingKey {
        case baseResp = "base_resp"
        case currentSubscribeTitle = "current_subscribe_title"
        case planName = "plan_name"
        case comboTitle = "combo_title"
        case currentPlanTitle = "current_plan_title"
        case currentComboCard = "current_combo_card"
        case pointsBalance = "points_balance"
        case pointBalance = "point_balance"
        case creditsBalance = "credits_balance"
        case creditBalance = "credit_balance"
        case balance
        case modelRemains = "model_remains"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.baseResp = try container.decodeIfPresent(MiniMaxBaseResponse.self, forKey: .baseResp)
        self.currentSubscribeTitle = try container.decodeIfPresent(String.self, forKey: .currentSubscribeTitle)
        self.planName = try container.decodeIfPresent(String.self, forKey: .planName)
        self.comboTitle = try container.decodeIfPresent(String.self, forKey: .comboTitle)
        self.currentPlanTitle = try container.decodeIfPresent(String.self, forKey: .currentPlanTitle)
        self.currentComboCard = try container.decodeIfPresent(MiniMaxComboCard.self, forKey: .currentComboCard)
        self.pointsBalance = MiniMaxDecoding.decodeDouble(container, forKeys: [
            .pointsBalance,
            .pointBalance,
            .creditsBalance,
            .creditBalance,
            .balance,
        ])
        self.modelRemains = try (container.decodeIfPresent([MiniMaxModelRemains].self, forKey: .modelRemains)) ?? []
    }
}

struct MiniMaxComboCard: Decodable {
    let title: String?
}

struct MiniMaxBaseResponse: Decodable {
    let statusCode: Int?
    let statusMessage: String?

    private enum CodingKeys: String, CodingKey {
        case statusCode = "status_code"
        case statusMessage = "status_msg"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.statusCode = MiniMaxDecoding.decodeInt(container, forKey: .statusCode)
        self.statusMessage = try container.decodeIfPresent(String.self, forKey: .statusMessage)
    }
}

// MARK: - Multi-Service API Response Structures

struct MiniMaxMultiServicePayload: Decodable {
    let data: MiniMaxMultiServiceData
}

struct MiniMaxMultiServiceData: Decodable {
    let services: [MiniMaxServiceItem]
}

struct MiniMaxServiceItem: Decodable {
    let serviceType: String?
    let windowType: String?
    let timeRange: String?
    let usage: Int?
    let limit: Int?
    let percent: Double?

    private enum CodingKeys: String, CodingKey {
        case serviceType = "service_type"
        case windowType = "window_type"
        case timeRange = "time_range"
        case usage
        case limit
        case percent
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.serviceType = try container.decodeIfPresent(String.self, forKey: .serviceType)
        self.windowType = try container.decodeIfPresent(String.self, forKey: .windowType)
        self.timeRange = try container.decodeIfPresent(String.self, forKey: .timeRange)
        self.usage = MiniMaxDecoding.decodeInt(container, forKey: .usage)
        self.limit = MiniMaxDecoding.decodeInt(container, forKey: .limit)
        // Handle both Double and String for percent (flexible parsing)
        if let percentDouble = try? container.decodeIfPresent(Double.self, forKey: .percent) {
            self.percent = percentDouble
        } else if let percentString = try? container.decodeIfPresent(String.self, forKey: .percent),
                  let percentValue = Double(percentString)
        {
            self.percent = percentValue
        } else {
            self.percent = nil
        }
    }
}

enum MiniMaxUsageParser {
    static func decodePayload(data: Data) throws -> MiniMaxCodingPlanPayload {
        let decoder = JSONDecoder()
        return try decoder.decode(MiniMaxCodingPlanPayload.self, from: data)
    }

    static func decodeMultiServicePayload(data: Data) throws -> MiniMaxMultiServicePayload {
        let decoder = JSONDecoder()
        return try decoder.decode(MiniMaxMultiServicePayload.self, from: data)
    }

    static func decodePayload(json: [String: Any]) throws -> MiniMaxCodingPlanPayload {
        let normalized = self.normalizeCodingPlanPayload(json)
        let data = try JSONSerialization.data(withJSONObject: normalized, options: [])
        return try self.decodePayload(data: data)
    }

    static func parseCodingPlanRemains(data: Data, now: Date = Date()) throws -> MiniMaxUsageSnapshot {
        do {
            if let multiServiceSnapshot = try self.parseMultiService(data: data, now: now) {
                return multiServiceSnapshot
            }
        } catch {
            // Log multi-service parsing failure but continue to single-service parsing
            MiniMaxUsageFetcher.log.debug("MiniMax multi-service parsing failed: \(error.localizedDescription)")
        }

        let payload = try self.decodePayload(data: data)
        return try self.parseCodingPlanRemains(payload: payload, now: now)
    }

    static func parse(html: String, now: Date = Date()) throws -> MiniMaxUsageSnapshot {
        if let snapshot = self.parseNextData(html: html, now: now) {
            return snapshot
        }
        let text = self.stripHTML(html)

        let planName = self.parsePlanName(html: html, text: text)
        let available = self.parseAvailableUsage(text: text)
        let usedPercent = self.parseUsedPercent(text: text)
        let resetsAt = self.parseResetsAt(text: text, now: now)

        if planName == nil, available == nil, usedPercent == nil {
            throw MiniMaxUsageError.parseFailed("Missing coding plan data.")
        }

        return MiniMaxUsageSnapshot(
            planName: planName,
            availablePrompts: available?.prompts,
            currentPrompts: nil,
            remainingPrompts: nil,
            windowMinutes: available?.windowMinutes,
            usedPercent: usedPercent,
            resetsAt: resetsAt,
            updatedAt: now)
    }

    static func parseCodingPlanRemains(
        payload: MiniMaxCodingPlanPayload,
        now: Date = Date()) throws -> MiniMaxUsageSnapshot
    {
        let baseResponse = payload.data.baseResp ?? payload.baseResp
        if let status = baseResponse?.statusCode, status != 0 {
            let message = baseResponse?.statusMessage ?? "status_code \(status)"
            let lower = message.lowercased()
            if status == 1004 || lower.contains("cookie") || lower.contains("log in") || lower.contains("login") {
                throw MiniMaxUsageError.invalidCredentials
            }
            throw MiniMaxUsageError.apiError(message)
        }

        guard !payload.data.modelRemains.isEmpty else {
            throw MiniMaxUsageError.parseFailed("Missing coding plan data.")
        }

        // Convert model_remains to services array for multi-service UI display
        var services: [MiniMaxServiceUsage] = []
        for item in payload.data.modelRemains {
            guard let modelName = item.modelName else { continue }
            let serviceTypeIdentifier = self.mapModelNameToServiceType(modelName: modelName)

            if let intervalService = self.makeServiceUsage(
                ServiceUsageInput(
                    serviceType: serviceTypeIdentifier,
                    windowTypeOverride: nil,
                    total: item.currentIntervalTotalCount,
                    remaining: item.currentIntervalUsageCount,
                    remainingPercent: item.currentIntervalRemainingPercent,
                    status: item.currentIntervalStatus,
                    start: item.startTime,
                    end: item.endTime,
                    remainsTime: item.remainsTime,
                    boostPermille: item.intervalBoostPermille),
                now: now)
            {
                services.append(intervalService)
            }

            // current_weekly_usage_count is also REMAINING quota; render only when weekly quota is real.
            if self.shouldRenderWeeklyWindow(for: modelName),
               let weeklyService = self.makeServiceUsage(
                   ServiceUsageInput(
                       serviceType: serviceTypeIdentifier,
                       windowTypeOverride: "Weekly",
                       total: item.currentWeeklyTotalCount,
                       remaining: item.currentWeeklyUsageCount,
                       remainingPercent: item.currentWeeklyRemainingPercent,
                       status: item.currentWeeklyStatus,
                       start: item.weeklyStartTime,
                       end: item.weeklyEndTime,
                       remainsTime: item.weeklyRemainsTime,
                       boostPermille: item.weeklyBoostPermille),
                   now: now)
            {
                services.append(weeklyService)
            }
        }

        // Use first service for backward compatibility fields
        let first = payload.data.modelRemains.first
        let hasPercentQuota = first?.currentIntervalRemainingPercent != nil
        let total = hasPercentQuota && first?.currentIntervalTotalCount == 0 ? nil : first?.currentIntervalTotalCount
        let remaining = hasPercentQuota && first?.currentIntervalUsageCount == 0
            ? nil
            : first?.currentIntervalUsageCount
        let usedPercent = self.usedPercent(
            total: total,
            remaining: remaining,
            remainingPercent: first?.currentIntervalRemainingPercent)

        let windowMinutes = self.windowMinutes(
            start: self.dateFromEpoch(first?.startTime),
            end: self.dateFromEpoch(first?.endTime))

        let resetsAt = self.resetsAt(
            end: self.dateFromEpoch(first?.endTime),
            remains: first?.remainsTime,
            now: now)

        let planName = self.parsePlanName(data: payload.data)

        let currentPrompts: Int? = if let total, let remaining {
            max(0, total - remaining)
        } else {
            nil
        }

        return MiniMaxUsageSnapshot(
            planName: planName,
            availablePrompts: total,
            currentPrompts: currentPrompts,
            remainingPrompts: remaining,
            windowMinutes: windowMinutes,
            usedPercent: usedPercent,
            resetsAt: resetsAt,
            updatedAt: now,
            services: services.isEmpty ? nil : services,
            pointsBalance: payload.data.pointsBalance)
    }

    private static func usedPercent(total: Int?, remaining: Int?, remainingPercent: Double? = nil) -> Double? {
        if let remainingPercent {
            return self.usedPercent(remainingPercent: remainingPercent)
        }
        guard let total, total > 0, let remaining else { return nil }
        let used = max(0, total - remaining)
        let percent = Double(used) / Double(total) * 100
        return min(100, max(0, percent))
    }

    private static func usedPercent(remainingPercent: Double) -> Double {
        min(100, max(0, 100 - remainingPercent))
    }

    private static func dateFromEpoch(_ value: Int?) -> Date? {
        guard let raw = value else { return nil }
        if raw > 1_000_000_000_000 {
            return Date(timeIntervalSince1970: TimeInterval(raw) / 1000)
        }
        if raw > 1_000_000_000 {
            return Date(timeIntervalSince1970: TimeInterval(raw))
        }
        return nil
    }

    private static func windowMinutes(start: Date?, end: Date?) -> Int? {
        guard let start, let end else { return nil }
        let minutes = Int(end.timeIntervalSince(start) / 60)
        return minutes > 0 ? minutes : nil
    }

    private static func resetsAt(end: Date?, remains: Int?, now: Date) -> Date? {
        if let end, end > now {
            return end
        }
        guard let remains, remains > 0 else { return nil }
        let seconds: TimeInterval = remains > 1_000_000 ? TimeInterval(remains) / 1000 : TimeInterval(remains)
        return now.addingTimeInterval(seconds)
    }

    private static func parsePlanName(data: MiniMaxCodingPlanData) -> String? {
        [
            data.currentSubscribeTitle,
            data.planName,
            data.comboTitle,
            data.currentPlanTitle,
            data.currentComboCard?.title,
        ]
            .compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) }
            .first { !$0.isEmpty } ?? self.inferredTokenPlanName(data: data)
    }

    private static func inferredTokenPlanName(data: MiniMaxCodingPlanData) -> String? {
        let hasTextGeneration = data.modelRemains.contains { $0.modelName.map(self.isTextGenerationModelName) ?? false }
        let hasUnavailableVideo = data.modelRemains.contains { item in
            item.modelName?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "video" &&
                self.isUnavailableQuotaPlaceholder(ServiceUsageInput(
                    serviceType: "Text to Video",
                    windowTypeOverride: nil,
                    total: item.currentIntervalTotalCount,
                    remaining: item.currentIntervalUsageCount,
                    remainingPercent: item.currentIntervalRemainingPercent,
                    status: item.currentIntervalStatus,
                    start: nil,
                    end: nil,
                    remainsTime: nil,
                    boostPermille: nil))
        }
        return hasTextGeneration && hasUnavailableVideo ? "Plus" : nil
    }

    private static func parsePlanName(html: String, text: String) -> String? {
        [
            self.extractFirst(pattern: #"(?i)"planName"\s*:\s*"([^"]+)""#, text: html),
            self.extractFirst(pattern: #"(?i)"plan"\s*:\s*"([^"]+)""#, text: html),
            self.extractFirst(pattern: #"(?i)"packageName"\s*:\s*"([^"]+)""#, text: html),
            self.extractFirst(pattern: #"(?i)Coding\s*Plan\s*([A-Za-z0-9][A-Za-z0-9\s._-]{0,32})"#, text: text),
        ]
            .compactMap(\.self)
            .map {
                UsageFormatter.cleanPlanName($0)
                    .replacingOccurrences(
                        of: #"(?i)\s+available\s+usage.*$"#,
                        with: "",
                        options: .regularExpression)
                    .trimmingCharacters(in: .whitespacesAndNewlines)
            }
            .first { !$0.isEmpty }
    }

    private static func parseNextData(html: String, now: Date) -> MiniMaxUsageSnapshot? {
        guard let data = self.nextDataJSONData(fromHTML: html),
              let object = try? JSONSerialization.jsonObject(with: data, options: []),
              let payload = self.findCodingPlanPayload(in: object),
              let decoded = try? self.decodePayload(json: payload)
        else {
            return nil
        }
        return try? self.parseCodingPlanRemains(payload: decoded, now: now)
    }

    private static func findCodingPlanPayload(in object: Any) -> [String: Any]? {
        if let dict = object as? [String: Any] {
            if dict["model_remains"] != nil || dict["modelRemains"] != nil {
                return dict
            }
            for value in dict.values {
                if let match = self.findCodingPlanPayload(in: value) {
                    return match
                }
            }
            return nil
        }

        if let array = object as? [Any] {
            for value in array {
                if let match = self.findCodingPlanPayload(in: value) {
                    return match
                }
            }
        }
        return nil
    }

    private static func normalizeCodingPlanPayload(_ payload: [String: Any]) -> [String: Any] {
        var normalized = payload

        if normalized["model_remains"] == nil, let value = normalized["modelRemains"] {
            normalized["model_remains"] = value
        }
        if normalized["current_subscribe_title"] == nil, let value = normalized["currentSubscribeTitle"] {
            normalized["current_subscribe_title"] = value
        }
        if normalized["plan_name"] == nil, let value = normalized["planName"] {
            normalized["plan_name"] = value
        }
        if normalized["combo_title"] == nil, let value = normalized["comboTitle"] {
            normalized["combo_title"] = value
        }
        if normalized["current_plan_title"] == nil, let value = normalized["currentPlanTitle"] {
            normalized["current_plan_title"] = value
        }
        if normalized["current_combo_card"] == nil, let value = normalized["currentComboCard"] {
            normalized["current_combo_card"] = value
        }
        if normalized["base_resp"] == nil, let value = normalized["baseResp"] {
            normalized["base_resp"] = value
        }
        if normalized["points_balance"] == nil, let value = normalized["pointsBalance"] {
            normalized["points_balance"] = value
        }
        if normalized["credits_balance"] == nil, let value = normalized["creditsBalance"] {
            normalized["credits_balance"] = value
        }

        if let data = normalized["data"] as? [String: Any] {
            normalized["data"] = self.normalizeCodingPlanPayload(data)
        }

        return normalized
    }

    private static let nextDataNeedle = Data("id=\"__NEXT_DATA__\"".utf8)
    private static let scriptCloseNeedle = Data("</script>".utf8)

    private static func nextDataJSONData(fromHTML html: String) -> Data? {
        let data = Data(html.utf8)
        guard let idRange = data.range(of: self.nextDataNeedle) else { return nil }
        guard let openTagEnd = data[idRange.upperBound...].firstIndex(of: UInt8(ascii: ">")) else { return nil }
        let contentStart = data.index(after: openTagEnd)
        guard let closeRange = data.range(
            of: self.scriptCloseNeedle,
            options: [],
            in: contentStart..<data.endIndex)
        else { return nil }
        let rawData = data[contentStart..<closeRange.lowerBound]
        let trimmed = self.trimASCIIWhitespace(Data(rawData))
        return trimmed.isEmpty ? nil : trimmed
    }

    private static func trimASCIIWhitespace(_ data: Data) -> Data {
        guard !data.isEmpty else { return data }
        var start = data.startIndex
        var end = data.endIndex

        while start < end, self.isASCIIWhitespace(data[start]) {
            start = data.index(after: start)
        }
        while end > start {
            let prev = data.index(before: end)
            if self.isASCIIWhitespace(data[prev]) {
                end = prev
            } else {
                break
            }
        }
        return data.subdata(in: start..<end)
    }

    private static func isASCIIWhitespace(_ value: UInt8) -> Bool {
        switch value {
        case 9, 10, 13, 32:
            true
        default:
            false
        }
    }

    private static func parseAvailableUsage(text: String) -> (prompts: Int, windowMinutes: Int)? {
        let pattern =
            #"(?i)available\s+usage[:\s]*([0-9][0-9,]*)\s*prompts?\s*/\s*"# +
            #"([0-9]+(?:\.[0-9]+)?)\s*(hours?|hrs?|h|minutes?|mins?|m|days?|d)"#
        guard let match = self.extractMatch(pattern: pattern, text: text), match.count >= 3 else { return nil }
        let promptsRaw = match[0]
        let durationRaw = match[1]
        let unitRaw = match[2]

        let prompts = Int(promptsRaw.replacingOccurrences(of: ",", with: "")) ?? 0
        guard prompts > 0 else { return nil }

        guard let duration = Double(durationRaw) else { return nil }
        let windowMinutes = self.minutes(from: duration, unit: unitRaw)
        guard windowMinutes > 0 else { return nil }
        return (prompts, windowMinutes)
    }

    private static func parseUsedPercent(text: String) -> Double? {
        let patterns = [
            #"(?i)([0-9]{1,3}(?:\.[0-9]+)?)\s*%\s*used"#,
            #"(?i)used\s*([0-9]{1,3}(?:\.[0-9]+)?)\s*%"#,
        ]
        for pattern in patterns {
            if let raw = self.extractFirst(pattern: pattern, text: text),
               let value = Double(raw),
               value >= 0,
               value <= 100
            {
                return value
            }
        }
        return nil
    }

    private static func parseResetsAt(text: String, now: Date) -> Date? {
        if let match = self.extractMatch(
            pattern: #"(?i)resets?\s+in\s+([0-9]+)\s*(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d)"#,
            text: text),
            match.count >= 2,
            let value = Double(match[0])
        {
            let unit = match[1]
            let seconds = self.seconds(from: value, unit: unit)
            return now.addingTimeInterval(seconds)
        }

        if let match = self.extractMatch(
            pattern: #"(?i)resets?\s+at\s+([0-9]{1,2}:[0-9]{2})(?:\s*\(([^)]+)\))?"#,
            text: text),
            match.count >= 1
        {
            let timeText = match[0]
            let tzText = match.count > 1 ? match[1] : nil
            return self.dateForTime(timeText, timeZoneHint: tzText, now: now)
        }

        return nil
    }

    private static func dateForTime(_ time: String, timeZoneHint: String?, now: Date) -> Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm"
        if let tzHint = timeZoneHint?.trimmingCharacters(in: .whitespacesAndNewlines),
           !tzHint.isEmpty
        {
            formatter.timeZone = self.timeZone(from: tzHint)
        }
        formatter.locale = Locale(identifier: "en_US_POSIX")

        guard let timeOnly = formatter.date(from: time) else { return nil }
        var calendar = Calendar(identifier: .gregorian)
        calendar.timeZone = formatter.timeZone ?? .current

        let nowComponents = calendar.dateComponents([.year, .month, .day], from: now)
        var targetComponents = calendar.dateComponents([.hour, .minute], from: timeOnly)
        targetComponents.year = nowComponents.year
        targetComponents.month = nowComponents.month
        targetComponents.day = nowComponents.day
        guard var candidate = calendar.date(from: targetComponents) else { return nil }

        if candidate < now {
            candidate = calendar.date(byAdding: .day, value: 1, to: candidate) ?? candidate
        }
        return candidate
    }

    private static func minutes(from value: Double, unit: String) -> Int {
        let lower = unit.lowercased()
        if lower.hasPrefix("d") { return Int((value * 24 * 60).rounded()) }
        if lower.hasPrefix("h") { return Int((value * 60).rounded()) }
        if lower.hasPrefix("m") { return Int(value.rounded()) }
        if lower.hasPrefix("s") { return max(1, Int((value / 60).rounded())) }
        return 0
    }

    private static func timeZone(from hint: String) -> TimeZone? {
        let trimmed = hint.trimmingCharacters(in: .whitespacesAndNewlines)
        if let timeZone = TimeZone(identifier: trimmed) {
            return timeZone
        }

        let pattern = #"(?i)^(?:UTC|GMT)\s*([+-])\s*(\d{1,2})(?::?(\d{2}))?$"#
        guard let regex = try? NSRegularExpression(pattern: pattern),
              let match = regex.firstMatch(in: trimmed, range: NSRange(trimmed.startIndex..., in: trimmed)),
              let signRange = Range(match.range(at: 1), in: trimmed),
              let hourRange = Range(match.range(at: 2), in: trimmed)
        else {
            return nil
        }

        let sign = trimmed[signRange] == "-" ? -1 : 1
        let hours = Int(trimmed[hourRange]) ?? 0
        let minutes = if match.range(at: 3).location != NSNotFound,
                         let minuteRange = Range(match.range(at: 3), in: trimmed)
        {
            Int(trimmed[minuteRange]) ?? 0
        } else {
            0
        }
        return TimeZone(secondsFromGMT: sign * ((hours * 3600) + (minutes * 60)))
    }

    private static func seconds(from value: Double, unit: String) -> TimeInterval {
        let lower = unit.lowercased()
        if lower.hasPrefix("d") { return value * 24 * 60 * 60 }
        if lower.hasPrefix("h") { return value * 60 * 60 }
        if lower.hasPrefix("m") { return value * 60 }
        return value
    }

    private static func stripHTML(_ html: String) -> String {
        var text = html.replacingOccurrences(of: "<[^>]+>", with: " ", options: .regularExpression)
        text = text.replacingOccurrences(of: "&nbsp;", with: " ")
        text = text.replacingOccurrences(of: "&amp;", with: "&")
        text = text.replacingOccurrences(of: "&lt;", with: "<")
        text = text.replacingOccurrences(of: "&gt;", with: ">")
        text = text.replacingOccurrences(of: #"\s+"#, with: " ", options: .regularExpression)
        return text.trimmingCharacters(in: .whitespacesAndNewlines)
    }

    private static func extractFirst(pattern: String, text: String) -> String? {
        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }
        let range = NSRange(text.startIndex..<text.endIndex, in: text)
        guard let match = regex.firstMatch(in: text, options: [], range: range),
              match.numberOfRanges >= 2,
              let captureRange = Range(match.range(at: 1), in: text)
        else { return nil }
        return String(text[captureRange]).trimmingCharacters(in: .whitespacesAndNewlines)
    }

    private static func extractMatch(pattern: String, text: String) -> [String]? {
        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }
        let range = NSRange(text.startIndex..<text.endIndex, in: text)
        guard let match = regex.firstMatch(in: text, options: [], range: range),
              match.numberOfRanges >= 2
        else { return nil }
        return (1..<match.numberOfRanges).compactMap { idx in
            guard let captureRange = Range(match.range(at: idx), in: text) else { return nil }
            return String(text[captureRange]).trimmingCharacters(in: .whitespacesAndNewlines)
        }
    }

    // MARK: - Multi-Service Parsing

    private static func parseMultiService(data: Data, now: Date) throws -> MiniMaxUsageSnapshot? {
        let payload = try self.decodeMultiServicePayload(data: data)

        guard !payload.data.services.isEmpty else {
            return nil
        }

        var services: [MiniMaxServiceUsage] = []
        for item in payload.data.services {
            guard let serviceType = item.serviceType,
                  let windowType = item.windowType,
                  let timeRange = item.timeRange,
                  let usage = item.usage,
                  let limit = item.limit,
                  limit > 0
            else {
                continue
            }

            var percent = item.percent ?? 0.0
            if item.percent == nil, limit > 0 {
                percent = Double(usage) / Double(limit) * 100.0
            }

            let resetsAt = self.parseResetsAtFromTimeRange(timeRange: timeRange, windowType: windowType, now: now)
            let resetDescription = self.resetDescription(
                for: windowType,
                timeRange: timeRange,
                now: now,
                resetsAt: resetsAt)

            let serviceTypeIdentifier: String = if serviceType.lowercased().contains("text"),
                                                   serviceType.lowercased().contains("generation")
            {
                "text-generation"
            } else if serviceType.lowercased().contains("text"), serviceType.lowercased().contains("speech") {
                "text-to-speech"
            } else if serviceType.lowercased().contains("image") {
                "image"
            } else {
                serviceType.lowercased()
                    .replacingOccurrences(of: " ", with: "-")
                    .replacingOccurrences(of: "_", with: "-")
            }

            let serviceUsage = MiniMaxServiceUsage(
                serviceType: serviceTypeIdentifier,
                windowType: windowType,
                timeRange: timeRange,
                usage: usage,
                limit: limit,
                percent: min(100.0, max(0.0, percent)),
                resetsAt: resetsAt,
                resetDescription: resetDescription)
            services.append(serviceUsage)
        }

        if services.isEmpty {
            return nil
        }

        let planName = self.extractPlanNameFromServices(services: payload.data.services)

        return MiniMaxUsageSnapshot(
            planName: planName,
            availablePrompts: nil,
            currentPrompts: nil,
            remainingPrompts: nil,
            windowMinutes: nil,
            usedPercent: nil,
            resetsAt: nil,
            updatedAt: now,
            services: services)
    }

    private static func parseResetsAtFromTimeRange(timeRange: String, windowType: String, now: Date) -> Date? {
        let lowerWindow = windowType.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)

        if lowerWindow == "today" {
            let components = timeRange.split(separator: "-", maxSplits: 1)
            guard components.count == 2 else { return nil }

            let endTimeStr = String(components[1].trimmingCharacters(in: .whitespacesAndNewlines))
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy/MM/dd HH:mm"
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.timeZone = TimeZone(identifier: "Asia/Shanghai")

            return formatter.date(from: endTimeStr)
        }

        if lowerWindow.contains("hour") || lowerWindow.contains("h") {
            let timeComponents = timeRange.split(separator: "-")
            guard timeComponents.count >= 2 else { return nil }

            let endTimePart = String(timeComponents[1])
            let endTimeClean = endTimePart.replacingOccurrences(of: "\\(.*\\)", with: "", options: .regularExpression)
                .trimmingCharacters(in: .whitespacesAndNewlines)

            return self.dateForTime(endTimeClean, timeZoneHint: "UTC+8", now: now)
        }

        return nil
    }

    private static func resetDescription(
        for windowType: String,
        timeRange: String,
        now: Date,
        resetsAt: Date?) -> String
    {
        if let resetsAt, resetsAt > now {
            let interval = resetsAt.timeIntervalSince(now)
            if interval < 60 {
                return "Resets in \(Int(interval)) seconds"
            } else if interval < 3600 {
                let minutes = Int(interval / 60)
                return "Resets in \(minutes) minute\(minutes == 1 ? "" : "s")"
            } else if interval < 86400 {
                let hours = Int(interval / 3600)
                return "Resets in \(hours) hour\(hours == 1 ? "" : "s")"
            } else {
                let days = Int(interval / 86400)
                return "Resets in \(days) day\(days == 1 ? "" : "s")"
            }
        }

        return "\(windowType): \(timeRange)"
    }

    private static func extractPlanNameFromServices(services: [MiniMaxServiceItem]) -> String? {
        for service in services {
            if let serviceType = service.serviceType,
               serviceType.lowercased().contains("pro") || serviceType.lowercased().contains("max")
            {
                return serviceType
            }
        }

        return nil
    }

    private static func parseWindowInfo(
        startTime: Date?,
        endTime: Date?,
        now: Date) -> (windowType: String, timeRange: String)
    {
        guard let startTime, let endTime else {
            return (windowType: "Unknown", timeRange: "N/A")
        }

        let durationSeconds = endTime.timeIntervalSince(startTime)
        let durationHours = durationSeconds / 3600

        // Determine window type based on duration
        let windowType = if durationHours >= 23, durationHours <= 25 {
            "Today"
        } else if durationHours >= 4, durationHours <= 6 {
            "5 hours"
        } else if durationHours >= 1, durationHours < 23 {
            "\(Int(durationHours)) hours"
        } else {
            "Custom"
        }

        // Format time range
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm"
        formatter.timeZone = TimeZone(identifier: "Asia/Shanghai") ?? .current
        let startStr = formatter.string(from: startTime)
        let endStr = formatter.string(from: endTime)

        let timeRange = "\(startStr)-\(endStr)(UTC+8)"

        return (windowType: windowType, timeRange: timeRange)
    }

    private struct ServiceUsageInput {
        let serviceType: String
        let windowTypeOverride: String?
        let total: Int?
        let remaining: Int?
        let remainingPercent: Double?
        let status: Int?
        let start: Int?
        let end: Int?
        let remainsTime: Int?
        let boostPermille: Int?
    }

    private static func makeServiceUsage(_ input: ServiceUsageInput, now: Date) -> MiniMaxServiceUsage? {
        guard self.shouldRenderQuotaWindow(input) else { return nil }

        let startTime = self.dateFromEpoch(input.start)
        let endTime = self.dateFromEpoch(input.end)
        var (windowType, timeRange) = self.parseWindowInfo(startTime: startTime, endTime: endTime, now: now)
        if let windowTypeOverride = input.windowTypeOverride { windowType = windowTypeOverride }
        if windowType.lowercased() == "weekly",
           let weeklyRange = self.formatMiniMaxDateTimeRange(startTime: startTime, endTime: endTime)
        {
            timeRange = weeklyRange
        }

        let isUnlimited = self.isUnlimitedQuotaWindow(input, windowType: windowType)
        let resetsAt = isUnlimited ? nil : self.resetsAt(end: endTime, remains: input.remainsTime, now: now)
        let resetDescription = if isUnlimited {
            "Unlimited"
        } else {
            self.resetDescription(
                for: windowType,
                timeRange: timeRange,
                now: now,
                resetsAt: resetsAt)
        }
        let limit: Int
        let usage: Int
        let percent: Double
        if isUnlimited {
            percent = 0
            limit = 0
            usage = 0
        } else if let remainingPercent = input.remainingPercent {
            let quotaLimit = self.percentQuotaLimit(boostPermille: input.boostPermille)
            percent = self.usedPercent(remainingPercent: remainingPercent)
            limit = quotaLimit
            usage = Int((percent * Double(quotaLimit) / 100.0).rounded())
        } else {
            guard let total = input.total, total > 0, let remaining = input.remaining else { return nil }
            let used = max(0, total - remaining)
            percent = Double(used) / Double(total) * 100.0
            limit = total
            usage = used
        }

        return MiniMaxServiceUsage(
            serviceType: input.serviceType,
            windowType: windowType,
            timeRange: timeRange,
            usage: usage,
            limit: limit,
            percent: min(100.0, max(0.0, percent)),
            isUnlimited: isUnlimited,
            resetsAt: resetsAt,
            resetDescription: resetDescription)
    }

    private static func percentQuotaLimit(boostPermille: Int?) -> Int {
        guard let boostPermille, boostPermille > 0 else { return 100 }
        return max(1, Int((Double(boostPermille) / 10.0).rounded()))
    }

    private static func shouldRenderQuotaWindow(_ input: ServiceUsageInput) -> Bool {
        // MiniMax Token Plan returns status 3 for quota lanes that exist in the schema but are not included in
        // the current subscription, for example Plus accounts receiving a video lane with 100% remaining and 0 count.
        !self.isUnavailableQuotaPlaceholder(input)
    }

    private static func isUnavailableQuotaPlaceholder(_ input: ServiceUsageInput) -> Bool {
        if let windowType = input.windowTypeOverride, self.isUnlimitedQuotaWindow(input, windowType: windowType) {
            return false
        }
        return input.status == 3 &&
            (input.total ?? 0) == 0 &&
            (input.remaining ?? 0) == 0 &&
            (input.remainingPercent.map { $0 >= 100 } ?? false)
    }

    private static func isUnlimitedQuotaWindow(_ input: ServiceUsageInput, windowType: String) -> Bool {
        let normalizedService = input.serviceType.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
        let normalizedWindow = windowType.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
        let unlimitedServices = ["text generation", "general"]
        return input.status == 3 &&
            unlimitedServices.contains(normalizedService) &&
            normalizedWindow == "weekly" &&
            (input.remainingPercent.map { $0 >= 100 } ?? false)
    }
}
