package com.tencent.qian.webview.prewarm

import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean

/**
 * manifest 拉取 / 缓存 / 新鲜度管理（进程内，由 Prewarmer 持有）。
 *
 * 策略（design.md Decision 5）：
 * - 内存缓存整个 session；TTL 读自响应头 `Cache-Control: max-age`，缺省 300s。
 * - [ensureFresh]：缺失则拉取并排队回调（pending replay）；已有但过期则后台重拉（stale-while-revalidate）。
 * - [isFetching] 保护：每 TTL 窗口最多一次重拉。
 * - 拉取失败静默 no-op。
 *
 * 回调在后台线程触发；涉及 WebView 的动作由调用方（Prewarmer/Pool）自行切主线程。
 */
internal class ManifestStore {

    @Volatile private var manifest: WarmupManifest? = null
    @Volatile private var lastFetchAtMs: Long = 0L
    @Volatile private var ttlMs: Long = 300_000L
    @Volatile private var manifestUrl: String = ""
    private val isFetching = AtomicBoolean(false)

    private val pending = ArrayList<(WarmupManifest) -> Unit>()
    private val pendingLock = Any()

    /** pending 回调队列上限。弱网下 ensureFresh 可能多次入队，设上限防御异常累积。 */
    private val maxPending = 16

    private val executor = Executors.newSingleThreadExecutor { r ->
        Thread(r, "qian-prewarm-manifest").apply { isDaemon = true }
    }

    /** version 变化钩子（用于去重重置），由 Prewarmer 注入。 */
    @Volatile var onVersionChanged: (() -> Unit)? = null

    fun setManifestUrl(url: String) {
        manifestUrl = url
    }

    fun getManifest(): WarmupManifest? = manifest

    fun isStale(): Boolean = (System.currentTimeMillis() - lastFetchAtMs) > ttlMs

    /**
     * 确保 manifest 可用且新鲜。
     * - 无缓存：触发拉取，[onReady] 排队等 manifest 到达后回调。
     * - 有缓存但过期：立即以当前缓存回调 + 后台重拉（不阻塞）。
     * - 有缓存且新鲜：立即以当前缓存回调。
     */
    fun ensureFresh(onReady: ((WarmupManifest) -> Unit)?) {
        val current = manifest
        if (current != null) {
            onReady?.invoke(current)
            if (isStale()) {
                fetch()
            }
            return
        }
        if (onReady != null) {
            // 上限保护：超限直接丢弃新回调，避免弱网下 pending 无界增长。
            synchronized(pendingLock) {
                if (pending.size < maxPending) pending.add(onReady)
            }
        }
        fetch()
    }

    /** 后台拉取（带 isFetching 保护，失败静默）。 */
    private fun fetch() {
        val url = manifestUrl
        if (url.isEmpty()) return
        if (!isFetching.compareAndSet(false, true)) return
        executor.execute {
            var conn: HttpURLConnection? = null
            var success = false
            try {
                conn = (URL(url).openConnection() as HttpURLConnection).apply {
                    requestMethod = "GET"
                    connectTimeout = 8000
                    readTimeout = 8000
                    setRequestProperty("Accept", "application/json")
                }
                val code = conn.responseCode
                if (code != 200) return@execute
                ttlMs = parseMaxAge(conn.getHeaderField("Cache-Control")) ?: 300_000L
                val body = conn.inputStream.bufferedReader().use { it.readText() }
                val parsed = WarmupManifest.parse(body) ?: return@execute
                val oldVersion = manifest?.version ?: ""
                val versionChanged = oldVersion.isNotEmpty() && oldVersion != parsed.version
                manifest = parsed
                lastFetchAtMs = System.currentTimeMillis()
                success = true
                // pending replay
                val callbacks: List<(WarmupManifest) -> Unit>
                synchronized(pendingLock) {
                    callbacks = ArrayList(pending)
                    pending.clear()
                }
                callbacks.forEach {
                    try {
                        it(parsed)
                    } catch (_: Throwable) {
                        // ignore
                    }
                }
                if (versionChanged) {
                    onVersionChanged?.invoke()
                }
            } catch (_: Throwable) {
                // 拉取/解析失败静默
            } finally {
                try {
                    conn?.disconnect()
                } catch (_: Throwable) {
                    // ignore
                }
                isFetching.set(false)
                // 拉取失败：清空 pending（符合"失败静默不回调"契约），避免回调无界累积。
                // 下次 ensureFresh 会重新入队重试；去重状态未更新故不会误判已预热。
                if (!success) {
                    synchronized(pendingLock) { pending.clear() }
                }
            }
        }
    }

    /** 解析 `Cache-Control: max-age=NNN` → 毫秒；无则返回 null。 */
    private fun parseMaxAge(headerValue: String?): Long? {
        if (headerValue.isNullOrEmpty()) return null
        val cc = headerValue.lowercase()
        val idx = cc.indexOf("max-age=")
        if (idx < 0) return null
        val rest = cc.substring(idx + 8)
        val token = rest.split(",")[0].trim()
        val seconds = token.toLongOrNull() ?: return null
        if (seconds <= 0) return null
        return seconds * 1000L
    }
}
