import Foundation

public enum MiniMaxProviderDescriptor {
    public static let descriptor: ProviderDescriptor = Self.makeDescriptor()
    private static let credentials = ProviderCredentialAdapter(
        supportsAPIKeyOverride: true,
        usesRegion: true,
        environmentProjections: [.apiKey(MiniMaxAPISettingsReader.apiTokenKey)],
        tokenResolver: { kind, environment, _ in
            let token: String? = switch kind {
            case .primary: MiniMaxAPISettingsReader.apiToken(environment: environment)
            case .secondary: MiniMaxSettingsReader.cookieHeader(environment: environment)
            case .projectID: nil
            }
            guard let token else { return nil }
            return ProviderTokenResolution(token: token, source: .environment)
        },
        tokenAccountSupport: TokenAccountSupport(
            title: "Session tokens",
            subtitle: "Store multiple MiniMax Cookie headers.",
            placeholder: "Cookie: …",
            injection: .cookieHeader,
            requiresManualCookieSource: true,
            cookieName: nil),
        diagnosticSummary: { _, _, environment, settings in
            let apiToken = MiniMaxAPISettingsReader.apiToken(environment: environment)
            let cookie = MiniMaxSettingsReader.cookieHeader(environment: environment)
                ?? CookieHeaderNormalizer.normalize(settings?.minimax?.manualCookieHeader)
            let mode = MiniMaxAuthMode.resolve(apiToken: apiToken, cookieHeader: cookie)
            return ProviderDiagnosticAuthSummary(
                configured: mode.usesAPIToken || mode.usesCookie,
                modes: mode == .none ? [] : [mode.description])
        },
        configValidator: ProviderCredentialAdapter.regionValidator(
            displayName: "MiniMax",
            isValid: { MiniMaxAPIRegion(rawValue: $0) != nil }),
        missingCredentialMessage: { _ in MiniMaxAPISettingsError.missingToken.errorDescription })

    static func makeDescriptor() -> ProviderDescriptor {
        ProviderDescriptor(
            id: .minimax,
            settingsSection: .init(
                MiniMaxProviderSettingsKey.self,
                cookieSettings: { settings in
                    CookieProviderSettings(
                        cookieSource: settings.cookieSource,
                        manualCookieHeader: settings.manualCookieHeader)
                },
                credentialSettings: { context in
                    let settings = context.cookieSettings(for: .minimax)
                    let region = context.config?.sanitizedRegion
                        .flatMap(MiniMaxAPIRegion.init(rawValue:)) ?? .global
                    return MiniMaxProviderSettings(
                        cookieSource: settings.cookieSource,
                        manualCookieHeader: settings.manualCookieHeader,
                        apiRegion: region)
                }),
            credentials: self.credentials,
            metadata: ProviderMetadata(
                id: .minimax,
                displayName: "MiniMax",
                sessionLabel: "Prompts",
                weeklyLabel: "Window",
                opusLabel: nil,
                supportsOpus: false,
                supportsCredits: false,
                creditsHint: "",
                toggleTitle: "Show MiniMax usage",
                cliName: "minimax",
                defaultEnabled: false,
                isPrimaryProvider: false,
                usesAccountFallback: false,
                sharePlanLabels: [
                    "free": "Free", "pro": "Pro", "plus": "Plus", "max": "Max", "ultra": "Ultra",
                    "minimax star": "MiniMax Star", "combo star": "Combo Star", "coding plan pro": "Coding Plan Pro",
                    "token plan pro": "Token Plan Pro", "token plan · tokenplanplus-年度会员": "Token Plan Plus",
                    "tokenplanplus-年度会员": "Token Plan Plus", "tokenplanmax-年度会员": "Token Plan Max",
                    "tokenplanultra-年度会员": "Token Plan Ultra",
                ],
                browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder,
                dashboardURL: "https://platform.minimax.io/user-center/payment/coding-plan?cycle_type=3",
                statusPageURL: nil),
            branding: ProviderBranding(
                iconStyle: .init(provider: .minimax),
                iconResourceName: "ProviderIcon-minimax",
                color: ProviderColor(red: 254 / 255, green: 96 / 255, blue: 60 / 255),
                confettiPalette: [
                    ProviderColor(hex: 0x181E25),
                    ProviderColor(hex: 0x86909C),
                    ProviderColor(hex: 0xF7F8FA),
                ]),
            tokenCost: ProviderTokenCostConfig(
                supportsTokenCost: false,
                noDataMessage: { "MiniMax cost summary is not supported." }),
            presentation: ProviderUsagePresentation(
                costPresenter: { snapshot in
                    let style: ProviderCostMenuCardStyle = snapshot.providerCost?.period == "MiniMax points balance"
                        ? .pointsBalance
                        : .generic
                    return ProviderCostPresentation(menuCardStyle: style)
                },
                automaticSelectionPrioritizesExhaustedWindow: false,
                menuBarWindowResolver: { context in
                    guard context.metric == .automatic else { return .unhandled }
                    return .resolved(ProviderUsagePresentation.mostConstrained(
                        context.snapshot.primary,
                        context.snapshot.secondary,
                        context.snapshot.tertiary))
                },
                optionalDetails: ProviderOptionalDetailsPresentation(
                    hiddenTitlesWithoutOptionalUsage: ["Billing history"])),
            fetchPlan: ProviderFetchPlan(
                sourceModes: [.auto, .web, .api],
                pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)),
            cli: ProviderCLIConfig(
                name: "minimax",
                aliases: ["mini-max"],
                versionDetector: nil,
                browserSupportExemption: { sourceMode, environment, _ in
                    // Standard sk-api keys select the Coding Plan web strategy; other tokens use HTTPS directly.
                    guard sourceMode == .auto, let environment,
                          MiniMaxAPISettingsReader.apiToken(environment: environment) != nil else { return false }
                    return MiniMaxAPISettingsReader.apiKeyKind(environment: environment) != .standard
                }))
    }

    private static func resolveStrategies(context: ProviderFetchContext) async -> [any ProviderFetchStrategy] {
        switch context.sourceMode {
        case .web:
            return [MiniMaxCodingPlanFetchStrategy()]
        case .api:
            return [MiniMaxAPIFetchStrategy()]
        case .cli, .oauth:
            return []
        case .auto:
            break
        }
        let apiToken = ProviderTokenResolver.token(for: .minimax, environment: context.env)
        let apiKeyKind = MiniMaxAPISettingsReader.apiKeyKind(token: apiToken)
        let authMode = MiniMaxAuthMode.resolve(
            apiToken: apiToken,
            cookieHeader: ProviderTokenResolver.token(for: .minimax, kind: .secondary, environment: context.env))
        if authMode.usesAPIToken {
            if apiKeyKind == .standard {
                return [MiniMaxCodingPlanFetchStrategy()]
            }
            return [MiniMaxAPIFetchStrategy(), MiniMaxCodingPlanFetchStrategy()]
        }
        return [MiniMaxCodingPlanFetchStrategy()]
    }
}

struct MiniMaxAPIFetchStrategy: ProviderFetchStrategy {
    let id: String = "minimax.api"
    let kind: ProviderFetchKind = .apiToken

    func isAvailable(_ context: ProviderFetchContext) async -> Bool {
        let authMode = MiniMaxAuthMode.resolve(
            apiToken: ProviderTokenResolver.token(for: .minimax, environment: context.env),
            cookieHeader: ProviderTokenResolver.token(for: .minimax, kind: .secondary, environment: context.env))
        if let kind = MiniMaxAPISettingsReader.apiKeyKind(environment: context.env),
           kind == .standard
        {
            return false
        }
        return authMode.usesAPIToken
    }

    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
        guard let apiToken = ProviderTokenResolver.token(for: .minimax, environment: context.env) else {
            throw MiniMaxAPISettingsError.missingToken
        }
        let region = context.settings?.minimax?.apiRegion ?? .global
        let usage = try await MiniMaxUsageFetcher.fetchUsage(apiToken: apiToken, region: region)
        return self.makeResult(
            usage: usage.toUsageSnapshot(),
            sourceLabel: "api")
    }

    func shouldFallback(on error: Error, context _: ProviderFetchContext) -> Bool {
        guard let minimaxError = error as? MiniMaxUsageError else { return false }
        switch minimaxError {
        case .invalidCredentials:
            return true
        case let .apiError(message):
            return message.contains("HTTP 404")
        case .networkError, .parseFailed:
            return false
        }
    }
}

struct MiniMaxCodingPlanFetchStrategy: ProviderFetchStrategy {
    let id: String = "minimax.web"
    let kind: ProviderFetchKind = .web
    private static let log = CodexBarLog.logger(LogCategories.provider(.minimax, scope: "web"))

    func isAvailable(_ context: ProviderFetchContext) async -> Bool {
        if Self.resolveCookieOverride(context: context) != nil {
            return true
        }
        #if os(macOS)
        if let cached = CookieHeaderCache.load(provider: .minimax),
           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
        {
            return true
        }
        guard Self.allowsBrowserCookieImport(context: context) else {
            return false
        }
        return MiniMaxCookieImporter.hasSession(browserDetection: context.browserDetection)
        #else
        return false
        #endif
    }

    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
        let fetchContext = FetchContext(
            region: context.settings?.minimax?.apiRegion ?? .global,
            environment: context.env,
            includeBillingHistory: context.includeOptionalUsage)
        if let override = Self.resolveCookieOverride(context: context) {
            Self.log.debug("Using MiniMax cookie header from settings/env")
            let snapshot = try await MiniMaxUsageFetcher.fetchUsage(
                cookieHeader: override.cookieHeader,
                authorizationToken: override.authorizationToken,
                groupID: override.groupID,
                region: fetchContext.region,
                environment: fetchContext.environment,
                includeBillingHistory: context.includeOptionalUsage)
            return self.makeResult(
                usage: snapshot.toUsageSnapshot(),
                sourceLabel: "web")
        }

        #if os(macOS)
        let tokenContext = Self.loadTokenContext(browserDetection: context.browserDetection)

        var lastError: Error?
        if let cached = CookieHeaderCache.load(provider: .minimax),
           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
        {
            switch await Self.attemptFetch(
                cookieHeader: cached.cookieHeader,
                sourceLabel: cached.sourceLabel,
                tokenContext: tokenContext,
                logLabel: "cached",
                fetchContext: fetchContext)
            {
            case let .success(snapshot):
                return self.makeResult(
                    usage: snapshot.toUsageSnapshot(),
                    sourceLabel: "web")
            case let .failure(error):
                lastError = error
                if Self.shouldTryNextBrowser(for: error) {
                    CookieHeaderCache.clear(provider: .minimax)
                } else {
                    throw error
                }
            }
        }

        guard Self.allowsBrowserCookieImport(context: context) else {
            if let lastError {
                throw lastError
            }
            throw MiniMaxSettingsError.missingCookie
        }

        let sessions = (try? MiniMaxCookieImporter.importSessions(
            browserDetection: context.browserDetection)) ?? []
        guard !sessions.isEmpty else {
            if let lastError {
                throw lastError
            }
            throw MiniMaxSettingsError.missingCookie
        }

        for session in sessions {
            switch await Self.attemptFetch(
                cookieHeader: session.cookieHeader,
                sourceLabel: session.sourceLabel,
                tokenContext: tokenContext,
                logLabel: "",
                fetchContext: fetchContext)
            {
            case let .success(snapshot):
                CookieHeaderCache.store(
                    provider: .minimax,
                    cookieHeader: session.cookieHeader,
                    sourceLabel: session.sourceLabel)
                return self.makeResult(
                    usage: snapshot.toUsageSnapshot(),
                    sourceLabel: "web")
            case let .failure(error):
                lastError = error
                if Self.shouldTryNextBrowser(for: error) {
                    Self.log.debug("MiniMax cookies invalid from \(session.sourceLabel), trying next browser")
                    continue
                }
                throw error
            }
        }

        if let lastError {
            throw lastError
        }
        #endif

        throw MiniMaxSettingsError.missingCookie
    }

    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
        false
    }

    static func allowsBrowserCookieImport(context: ProviderFetchContext) -> Bool {
        context.runtime == .app && ProviderInteractionContext.current == .userInitiated
    }

    private struct TokenContext {
        let tokensByLabel: [String: [String]]
        let groupIDByLabel: [String: String]
    }

    private struct FetchContext {
        let region: MiniMaxAPIRegion
        let environment: [String: String]
        let includeBillingHistory: Bool
    }

    private enum FetchAttemptResult {
        case success(MiniMaxUsageSnapshot)
        case failure(Error)
    }

    private static func resolveCookieOverride(context: ProviderFetchContext) -> MiniMaxCookieOverride? {
        if let settings = context.settings?.minimax {
            guard settings.cookieSource == .manual else { return nil }
            return MiniMaxCookieHeader.override(from: settings.manualCookieHeader)
        }
        guard let raw = ProviderTokenResolver.token(for: .minimax, kind: .secondary, environment: context.env) else {
            return nil
        }
        return MiniMaxCookieHeader.override(from: raw)
    }

    private static func normalizeStorageLabel(_ label: String) -> String {
        let suffixes = [" (Session Storage)", " (IndexedDB)"]
        for suffix in suffixes where label.hasSuffix(suffix) {
            return String(label.dropLast(suffix.count))
        }
        return label
    }

    private static func loadTokenContext(browserDetection: BrowserDetection) -> TokenContext {
        #if os(macOS)
        let tokenLog: (String) -> Void = { msg in Self.log.debug(msg) }
        let accessTokens = MiniMaxLocalStorageImporter.importAccessTokens(
            browserDetection: browserDetection,
            logger: tokenLog)
        let groupIDs = MiniMaxLocalStorageImporter.importGroupIDs(
            browserDetection: browserDetection,
            logger: tokenLog)
        var tokensByLabel: [String: [String]] = [:]
        var groupIDByLabel: [String: String] = [:]
        for token in accessTokens {
            let normalized = Self.normalizeStorageLabel(token.sourceLabel)
            tokensByLabel[normalized, default: []].append(token.accessToken)
            if let groupID = token.groupID, groupIDByLabel[normalized] == nil {
                groupIDByLabel[normalized] = groupID
            }
        }
        for (label, groupID) in groupIDs {
            let normalized = Self.normalizeStorageLabel(label)
            if groupIDByLabel[normalized] == nil {
                groupIDByLabel[normalized] = groupID
            }
        }
        return TokenContext(tokensByLabel: tokensByLabel, groupIDByLabel: groupIDByLabel)
        #else
        _ = browserDetection
        return TokenContext(tokensByLabel: [:], groupIDByLabel: [:])
        #endif
    }

    private static func attemptFetch(
        cookieHeader: String,
        sourceLabel: String,
        tokenContext: TokenContext,
        logLabel: String,
        fetchContext: FetchContext) async -> FetchAttemptResult
    {
        let normalizedLabel = Self.normalizeStorageLabel(sourceLabel)
        let tokenCandidates = tokenContext.tokensByLabel[normalizedLabel] ?? []
        let groupID = tokenContext.groupIDByLabel[normalizedLabel]
        let cookieToken = Self.cookieValue(named: "HERTZ-SESSION", in: cookieHeader)
        var attempts: [String?] = tokenCandidates.map(\.self)
        if let cookieToken, !tokenCandidates.contains(cookieToken) {
            attempts.append(cookieToken)
        }
        attempts.append(nil)

        let prefix = logLabel.isEmpty ? "" : "\(logLabel) "
        var lastError: Error?
        for token in attempts {
            let tokenLabel: String = {
                guard let token else { return "" }
                if token == cookieToken {
                    return " + HERTZ-SESSION bearer"
                }
                return " + access token"
            }()
            Self.log.debug("Trying MiniMax \(prefix)cookies from \(sourceLabel)\(tokenLabel)")
            do {
                let snapshot = try await MiniMaxUsageFetcher.fetchUsage(
                    cookieHeader: cookieHeader,
                    authorizationToken: token,
                    groupID: groupID,
                    region: fetchContext.region,
                    environment: fetchContext.environment,
                    includeBillingHistory: fetchContext.includeBillingHistory)
                Self.log.debug("MiniMax \(prefix)cookies valid from \(sourceLabel)")
                return .success(snapshot)
            } catch {
                lastError = error
                if Self.shouldTryNextBrowser(for: error) {
                    continue
                }
                return .failure(error)
            }
        }

        if let lastError {
            return .failure(lastError)
        }
        return .failure(MiniMaxSettingsError.missingCookie)
    }

    private static func cookieValue(named name: String, in header: String) -> String? {
        let parts = header.split(separator: ";")
        for part in parts {
            let trimmed = part.trimmingCharacters(in: .whitespacesAndNewlines)
            guard trimmed.lowercased().hasPrefix("\(name.lowercased())=") else { continue }
            return String(trimmed.dropFirst(name.count + 1))
        }
        return nil
    }

    private static func shouldTryNextBrowser(for error: Error) -> Bool {
        if case MiniMaxUsageError.invalidCredentials = error {
            return true
        }
        if case MiniMaxUsageError.parseFailed = error {
            return true
        }
        return false
    }
}
