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

#if os(macOS)
import SweetCookieKit
#endif

#if os(macOS)
private let t3ChatCookieImportOrder: BrowserCookieImportOrder =
    ProviderDefaults.metadata[.t3chat]?.browserCookieOrder ?? Browser.defaultImportOrder

public enum T3ChatCookieImporter {
    private static let cookieClient = BrowserCookieClient()
    private static let cookieDomains = ["t3.chat", "www.t3.chat"]

    public struct SessionInfo: Sendable {
        public let cookieHeader: String
        public let sourceLabel: String

        public init(cookieHeader: String, sourceLabel: String) {
            self.cookieHeader = cookieHeader
            self.sourceLabel = sourceLabel
        }
    }

    public static func importSession(
        browserDetection: BrowserDetection,
        logger: ((String) -> Void)? = nil) throws -> SessionInfo
    {
        let log: (String) -> Void = { msg in logger?("[t3chat-cookie] \(msg)") }
        let installed = t3ChatCookieImportOrder.cookieImportCandidates(using: browserDetection)

        for browserSource in installed {
            do {
                let query = BrowserCookieQuery(domains: self.cookieDomains)
                let sources = try self.cookieClient.codexBarRecords(
                    matching: query,
                    in: browserSource,
                    logger: log)
                for source in sources where !source.records.isEmpty {
                    let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)
                    guard !cookies.isEmpty else { continue }
                    let names = cookies.map(\.name).joined(separator: ", ")
                    log("\(source.label) cookies: \(names)")
                    let header = cookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; ")
                    return SessionInfo(cookieHeader: header, sourceLabel: source.label)
                }
            } catch {
                BrowserCookieAccessGate.recordIfNeeded(error)
                log("\(browserSource.displayName) cookie import failed: \(error.localizedDescription)")
            }
        }

        throw T3ChatUsageError.noSessionCookie
    }
}
#endif

public struct T3ChatUsageFetcher: Sendable {
    private static let log = CodexBarLog.logger(LogCategories.provider(.t3chat))
    private static let baseURL = URL(string: "https://t3.chat")!
    private static let refererURL = URL(string: "https://t3.chat/settings/customization")!
    /// Browser fingerprint defaults are only fallbacks; full cURL captures override these forwarded headers.
    private static 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"
    /// Captured from T3 Chat's getCustomerData tRPC request shape in May 2026.
    private static let input = #"{"0":{"json":{"sessionId":null},"meta":{"values":{"sessionId":["undefined"]}}}}"#
    private static let forwardedManualHeaders = [
        "accept": "Accept",
        "accept-language": "Accept-Language",
        "cache-control": "Cache-Control",
        "pragma": "Pragma",
        "priority": "Priority",
        "referer": "Referer",
        "sec-fetch-dest": "Sec-Fetch-Dest",
        "sec-fetch-mode": "Sec-Fetch-Mode",
        "sec-fetch-site": "Sec-Fetch-Site",
        "trpc-accept": "trpc-accept",
        "user-agent": "User-Agent",
        "x-client-context": "x-client-context",
        "x-deployment-id": "X-Deployment-Id",
        "x-trpc-batch": "x-trpc-batch",
        "x-trpc-source": "x-trpc-source",
    ]

    public struct RequestContext: Sendable {
        public let cookieHeader: String
        public let headers: [String: String]

        public init(cookieHeader: String, headers: [String: String] = [:]) {
            self.cookieHeader = cookieHeader
            self.headers = headers
        }
    }

    public let browserDetection: BrowserDetection

    public init(browserDetection: BrowserDetection) {
        self.browserDetection = browserDetection
    }

    public func fetch(
        cookieHeaderOverride: String? = nil,
        timeout: TimeInterval = 15,
        logger: ((String) -> Void)? = nil,
        now: Date = Date(),
        transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> T3ChatUsageSnapshot
    {
        let log: (String) -> Void = { msg in logger?("[t3chat] \(msg)") }
        let context = try await self.resolveRequestContext(override: cookieHeaderOverride, logger: log)
        if let logger {
            let names = CookieHeaderNormalizer.pairs(from: context.cookieHeader).map(\.name)
            if !names.isEmpty {
                logger("[t3chat] Cookie names: \(names.joined(separator: ", "))")
            }
            if !context.headers.isEmpty {
                let headerNames = context.headers.keys.sorted().joined(separator: ", ")
                logger("[t3chat] Forwarding captured headers: \(headerNames)")
            }
        }
        return try await Self.fetchCustomerData(
            context: context,
            timeout: timeout,
            now: now,
            transport: transport)
    }

    public func debugRawProbe(cookieHeaderOverride: String? = nil) async -> String {
        let stamp = ISO8601DateFormatter().string(from: Date())
        var lines: [String] = []
        lines.append("=== T3 Chat Debug Probe @ \(stamp) ===")
        lines.append("")

        do {
            let snapshot = try await self.fetch(
                cookieHeaderOverride: cookieHeaderOverride,
                logger: { msg in lines.append(msg) })
            lines.append("")
            lines.append("Fetch Success")
            lines.append("subTier=\(snapshot.customerData.subTier ?? "nil")")
            lines.append("usageBand=\(snapshot.customerData.usageBand ?? "nil")")
            lines
                .append(
                    "usageFourHourPercentage=\(snapshot.customerData.usageFourHourPercentage?.description ?? "nil")")
            lines.append("usageMonthPercentage=\(snapshot.customerData.usageMonthPercentage?.description ?? "nil")")
            lines.append("usagePeriodPercentage=\(snapshot.customerData.usagePeriodPercentage?.description ?? "nil")")
            lines
                .append(
                    "usageFourHourNextResetAt=\(snapshot.customerData.usageFourHourNextResetAt?.description ?? "nil")")
            lines.append("billingNextResetAt=\(snapshot.customerData.billingNextResetAt?.description ?? "nil")")
        } catch {
            lines.append("")
            lines.append("Probe Failed: \(error.localizedDescription)")
        }

        return lines.joined(separator: "\n")
    }

    public static func fetchCustomerData(
        cookieHeader: String,
        timeout: TimeInterval = 15,
        now: Date = Date(),
        transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> T3ChatUsageSnapshot
    {
        guard let normalizedCookieHeader = CookieHeaderNormalizer.normalize(cookieHeader) else {
            throw T3ChatUsageError.noSessionCookie
        }
        return try await self.fetchCustomerData(
            context: RequestContext(cookieHeader: normalizedCookieHeader),
            timeout: timeout,
            now: now,
            transport: transport)
    }

    public static func fetchCustomerData(
        context: RequestContext,
        timeout: TimeInterval = 15,
        now: Date = Date(),
        transport: any ProviderHTTPTransport = ProviderHTTPClient.shared) async throws -> T3ChatUsageSnapshot
    {
        guard let normalizedCookieHeader = CookieHeaderNormalizer.normalize(context.cookieHeader) else {
            throw T3ChatUsageError.noSessionCookie
        }

        let url = try self.customerDataURL()
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.timeoutInterval = timeout
        self.applyDefaultHeaders(to: &request)
        for (name, value) in context.headers {
            request.setValue(value, forHTTPHeaderField: name)
        }
        request.setValue(self.baseURL.absoluteString, forHTTPHeaderField: "Origin")
        request.setValue(normalizedCookieHeader, forHTTPHeaderField: "Cookie")

        let response = try await transport.response(for: request)
        let data = response.data
        guard response.statusCode == 200 else {
            let body = String(data: data.prefix(200), encoding: .utf8) ?? "<binary>"
            Self.log.error("T3 Chat API returned \(response.statusCode): \(body)")
            if response.statusCode == 401 || response.statusCode == 403 {
                throw T3ChatUsageError.invalidCredentials
            }
            if response.statusCode == 429,
               response.response.value(forHTTPHeaderField: "x-vercel-mitigated") == "challenge"
            {
                throw T3ChatUsageError.vercelChallenge
            }
            throw T3ChatUsageError.apiError("HTTP \(response.statusCode)")
        }

        do {
            return try T3ChatUsageParser.parseJSONLines(data, now: now)
        } catch {
            let preview = String(data: data.prefix(500), encoding: .utf8) ?? "<binary>"
            Self.log.error("T3 Chat parse failed: \(error.localizedDescription) response=\(preview)")
            throw error
        }
    }

    private func resolveRequestContext(
        override: String?,
        logger: ((String) -> Void)?) async throws -> RequestContext
    {
        if let override = Self.requestContext(from: override) {
            let source = override.headers.isEmpty ? "manual cookie header" : "manual cURL capture"
            logger?("[t3chat] Using \(source)")
            return override
        }

        #if os(macOS)
        let session = try T3ChatCookieImporter.importSession(
            browserDetection: self.browserDetection,
            logger: logger)
        logger?("[t3chat] Using cookies from \(session.sourceLabel)")
        return RequestContext(cookieHeader: session.cookieHeader)
        #else
        throw T3ChatUsageError.noSessionCookie
        #endif
    }

    static func requestContext(from raw: String?) -> RequestContext? {
        guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { return nil }
        let headerFields = CurlCaptureParser.headerFields(from: raw)
        guard let cookieHeader = Self.cookieHeader(from: headerFields) ?? CookieHeaderNormalizer.normalize(raw) else {
            return nil
        }
        let headers = CurlCaptureParser.forwardedHeaders(from: headerFields, allowlist: self.forwardedManualHeaders)
        return RequestContext(cookieHeader: cookieHeader, headers: headers)
    }

    private static func applyDefaultHeaders(to request: inout URLRequest) {
        request.setValue("*/*", forHTTPHeaderField: "Accept")
        request.setValue("application/jsonl", forHTTPHeaderField: "trpc-accept")
        request.setValue("web-client", forHTTPHeaderField: "x-trpc-source")
        request.setValue("true", forHTTPHeaderField: "x-trpc-batch")
        request.setValue("en-US,en;q=0.9", forHTTPHeaderField: "Accept-Language")
        request.setValue(self.userAgent, forHTTPHeaderField: "User-Agent")
        request.setValue(self.refererURL.absoluteString, forHTTPHeaderField: "Referer")
        request.setValue("empty", forHTTPHeaderField: "Sec-Fetch-Dest")
        request.setValue("cors", forHTTPHeaderField: "Sec-Fetch-Mode")
        request.setValue("same-origin", forHTTPHeaderField: "Sec-Fetch-Site")
        request.setValue("u=4", forHTTPHeaderField: "Priority")
        request.setValue("no-cache", forHTTPHeaderField: "Pragma")
        request.setValue("no-cache", forHTTPHeaderField: "Cache-Control")
    }

    private static func cookieHeader(from fields: [String]) -> String? {
        guard let raw = CurlCaptureParser.headerValue(named: "Cookie", in: fields) else { return nil }
        return CookieHeaderNormalizer.normalize(raw)
    }

    private static func customerDataURL() throws -> URL {
        var components = URLComponents(string: "https://t3.chat/api/trpc/getCustomerData")!
        components.queryItems = [
            URLQueryItem(name: "batch", value: "1"),
            URLQueryItem(name: "input", value: self.input),
        ]
        guard let url = components.url else {
            throw T3ChatUsageError.apiError("Failed to build customer data URL.")
        }
        return url
    }
}
