import Foundation
import WebKit

/// **WebView 预热统一入口**（三端对齐）。
///
/// 两个正交维度：
/// - 网络预热（L1 DNS / L4 warmup.html）——省网络加载时间。
/// - 进程预热——预建实例触发 WebContent 进程启动，页面新建 WKWebView 复用同一 `processPool`。
///
/// **iOS 平台说明**：MINIMAL = 纯 DNS；LIGHT = 离屏预建空实例（触发 WebContent 进程启动）；
/// HEAVY = 预热实例加载 `warmup.html`(L4)。`WKURLSchemeHandler` 拦不了 https，L2/L3 无轻量方案（Non-goal）。
///
/// **预热实例不复用到页面**：iOS `WKWebView` 无 clear-history API，复用加载过 warmup.html 的实例
/// 会导致 warmup.html 残留在 `backForwardList` 中（返回键退回 warmup 页）且无法清除。故容器每次
/// 新建独立 WKWebView，预热收益全部由共享 `processPool`（进程复用）+ `.default()` DataStore
/// （磁盘缓存跨实例命中，warmup.html 与真实页同 host）承接。
///
/// **典型用法**：
/// ```swift
/// // App 启动
/// TencentQianWebViewPrewarmer.configure()
/// TencentQianWebViewPrewarmer.prewarm(.minimal)
/// // 列表曝光 / 已知业务
/// TencentQianWebViewPrewarmer.prewarm(.light, bizType: "single-sign")
/// // 强意图
/// TencentQianWebViewPrewarmer.prewarm(.heavy, bizType: "single-sign")
/// ```
///
/// **跨端等价**：HarmonyOS / Android `TencentQianWebViewPrewarmer`。
public enum TencentQianWebViewPrewarmer {

    private static let store = ManifestStore()
    private static let dedup = PrewarmDedup()
    private static var config = PrewarmConfig.default
    private static var configured = false
    private static let stateLock = NSLock()

    /// 全局配置。App 启动时调用；触发 manifest 后台异步拉取（不阻塞）。
    ///
    /// 可省——未调用时首次 ``prewarm(_:bizType:)`` 会以默认配置（`.prod`）懒初始化。
    public static func configure(_ config: PrewarmConfig = .default) {
        var cfg = config
        if cfg.poolSize > 1 {
            NSLog("[TencentQianWebViewPrewarmer] poolSize > 1 not supported in v1, clamped to 1")
            cfg.poolSize = 1
        }
        stateLock.lock()
        self.config = cfg
        self.configured = true
        stateLock.unlock()

        WebViewPool.shared.setEnabled(cfg.enablePool)
        // 内存告警释放预热实例后重置去重状态，使后续 prewarm 可重新预热（与 Android/HarmonyOS 对齐）。
        WebViewPool.shared.onReleasedByMemoryWarning = { dedup.reset() }

        let url = (cfg.manifestUrl?.isEmpty == false) ? cfg.manifestUrl! : cfg.environment.manifestUrl
        store.setManifestUrl(url)
        store.onVersionChanged = { dedup.reset() }
        // 后台异步拉取 manifest
        store.ensureFresh(nil)
    }

    /// 按意图档位触发预热，可多次调用（内部单调去重）。
    ///
    /// - Parameters:
    ///   - level: 预热档位
    ///   - bizType: 业务类型；`.minimal` 可省，`.light`/`.heavy` 必填
    public static func prewarm(_ level: PrewarmLevel, bizType: String? = nil) {
        if !isConfigured() {
            lazyInit()
        }
        // bizType 允许为空：空时预热公共资源（warmup.html 加载公共部分），去重按空 key 记账
        let bt = bizType ?? ""
        // 先用缓存（可能略旧）立即预热；缺失则排队 replay；过期则后台重拉（SWR）
        store.ensureFresh { manifest in
            guard dedup.shouldRun(level, bizType: bt) else { return }
            execute(level, bizType: bt, manifest: manifest)
            dedup.markDone(level, bizType: bt)
        }
    }

    /// 释放池实例。
    public static func release() {
        WebViewPool.shared.release()
    }

    /// 释放所有预热资源并重置去重状态。
    public static func releaseAll() {
        WebViewPool.shared.release()
        dedup.reset()
    }

    /// `open(url)` 环境错配检测（best-effort，仅告警）。
    ///
    /// 仅当 url host 命中 3 个已知 `quick.*` 环境之一、且与配置环境不一致时才 log 警告；
    /// 非 `quick.*` 域名（客户自有/其他）不告警——`open()` 本就支持任意域名。
    public static func warnIfEnvironmentMismatch(_ url: String?) {
        guard isConfigured(), let env = PrewarmEnvironment.of(url: url) else { return }
        stateLock.lock()
        let configured = config.environment
        stateLock.unlock()
        if env != configured {
            NSLog("[TencentQianWebViewPrewarmer] environment mismatch: configured=\(configured) but open() host indicates \(env) — prewarm cache may miss")
        }
    }

    /// 一次性获取 manifest 中的 bizType 列表。
    ///
    /// - manifest 已缓存 → 立即回调
    /// - manifest 拉取中 → 排队，拉取完成后回调一次
    /// - 拉取失败 → 不回调（调用方需自行兜底）
    ///
    /// 未调用 ``configure(_:)`` 时会以默认配置（`.prod`）懒初始化，避免 callback 滞留。
    public static func getMenuBizTypes(_ callback: @escaping ([String]) -> Void) {
        if !isConfigured() {
            lazyInit()
        }
        store.ensureFresh { manifest in
            callback(manifest.menuBizTypes ?? Array(manifest.bizTypes.keys))
        }
    }

    // MARK: - Internal

    private static func isConfigured() -> Bool {
        stateLock.lock(); defer { stateLock.unlock() }
        return configured
    }

    private static func lazyInit() {
        stateLock.lock()
        configured = true
        let env = config.environment
        stateLock.unlock()
        store.setManifestUrl(env.manifestUrl)
        store.onVersionChanged = { dedup.reset() }
        // 内存告警释放预热实例后重置去重状态（与 configure 一致，覆盖懒初始化路径）。
        WebViewPool.shared.onReleasedByMemoryWarning = { dedup.reset() }
    }

    private static func execute(_ level: PrewarmLevel, bizType: String, manifest: WarmupManifest) {
        // L1：DNS 预解析全局 hosts（所有档位地基）
        prefetchDns(manifest.hosts)

        if level == .minimal {
            return
        }
        guard let biz = manifest.bizTypes[bizType] else { return }
        let (injectUA, uaExtra) = uaConfig()
        switch level {
        case .light:
            // 离屏预建空实例（触发 WebContent 进程启动 + 保活），按 UA 配置建；iOS 无轻量 L2/L3
            WebViewPool.shared.preCreate(injectUserAgent: injectUA, userAgentExtra: uaExtra)
        case .heavy:
            // 预热实例加载 warmup.html（L4，写入共享磁盘缓存跨实例命中）；不复用到页面
            WebViewPool.shared.warm(
                warmupUrl: appendLang(biz.warmupUrl, preferredLanguage()),
                injectUserAgent: injectUA,
                userAgentExtra: uaExtra
            )
        case .minimal:
            break
        }
    }

    /// 读取 UA 配置（加锁）。
    private static func uaConfig() -> (Bool, String) {
        stateLock.lock(); defer { stateLock.unlock() }
        return (config.injectUserAgent, config.userAgentExtra)
    }

    /// 读取语言偏好（加锁）。
    private static func preferredLanguage() -> String? {
        stateLock.lock(); defer { stateLock.unlock() }
        return config.preferredLanguage
    }

    /// 将语言偏好拼进 warmup.html URL（`?lang=`，warmup.html 内优先级最高）；空则原样返回。
    static func appendLang(_ url: String, _ lang: String?) -> String {
        guard let lang = lang, !lang.isEmpty else { return url }
        let sep = url.contains("?") ? "&" : "?"
        let encoded = lang.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? lang
        return "\(url)\(sep)lang=\(encoded)"
    }

    private static func prefetchDns(_ hosts: [String]) {
        for h in hosts {
            let host = hostOf(h)
            guard !host.isEmpty else { continue }
            DispatchQueue.global(qos: .utility).async {
                let ref = CFHostCreateWithName(nil, host as CFString).takeRetainedValue()
                CFHostStartInfoResolution(ref, .addresses, nil)
            }
        }
    }

    private static func hostOf(_ urlOrHost: String) -> String {
        if urlOrHost.contains("://") {
            return URL(string: urlOrHost)?.host ?? ""
        }
        // 纯 host：去掉可能的 path / port
        var s = urlOrHost
        if let slash = s.firstIndex(of: "/") { s = String(s[..<slash]) }
        if let colon = s.firstIndex(of: ":") { s = String(s[..<colon]) }
        return s
    }
}
