package app.dudu.data

import android.content.Context
import android.os.Build
import app.dudu.BuildConfig
import app.dudu.net.DuduApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.json.JSONArray
import org.json.JSONObject
import java.io.File

/**
 * 埋点队列：本地 JSONL 文件（设计文档 9.2：满批/定时批量上报，失败不影响功能）。
 * IME Service 与主 App 同进程共用此单例（设计文档 9.3 Android 行），
 * flush 由 WorkManager 周期触发（见 MetricsFlushWorker）+ 冷启动一次。
 *
 * 隐私底线：事件 props 不携带识别文本与音频。
 */
class MetricsQueue private constructor(private val context: Context) {

    private val file = File(context.filesDir, "metrics_queue.jsonl")
    private val prefs = context.getSharedPreferences("dudu_metrics", Context.MODE_PRIVATE)
    private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private val lock = Any()

    /** 记一条事件（异步落盘，任意线程可调）。未在白名单的事件名直接丢弃。 */
    fun track(event: String, props: Map<String, Any?> = emptyMap()) {
        if (event !in WHITELIST) return
        val line = JSONObject()
            .put("event", event)
            .put("client_ts", System.currentTimeMillis())
            .apply { if (props.isNotEmpty()) put("props", JSONObject(props)) }
            .toString()
        ioScope.launch { synchronized(lock) { append(line) } }
    }

    /** 低频漏斗事件去重：同 key 只记一次（如 kb.enabled）。 */
    fun trackOnce(key: String, event: String, props: Map<String, Any?> = emptyMap()) {
        if (prefs.getBoolean("once_$key", false)) return
        prefs.edit().putBoolean("once_$key", true).apply()
        track(event, props)
    }

    /**
     * 把队列分批（≤100 条/批，对齐 protocol.MetricsMaxBatch）POST /v1/metrics/batch（gzip）。
     * @return 全部发送成功（队列空也算成功）；失败由调用方（Worker）退避重试
     */
    suspend fun flush(): Boolean {
        val pending = synchronized(lock) {
            if (!file.exists()) emptyList() else file.readLines().filter { it.isNotBlank() }
        }
        if (pending.isEmpty()) return true

        val account = AccountRepository.get(context)
        var sent = 0
        for (chunk in pending.chunked(MAX_BATCH)) {
            val events = JSONArray()
            chunk.forEach { line -> runCatching { events.put(JSONObject(line)) } }
            val body = JSONObject()
                .put("device_id", account.deviceId)
                .put("platform", "android")
                .put("app_version", BuildConfig.VERSION_NAME)
                .put("os_version", "Android " + Build.VERSION.RELEASE)
                .put("events", events)
                .toString()
            if (!DuduApi.metricsBatch(body)) break
            sent += chunk.size
        }
        if (sent > 0) {
            synchronized(lock) {
                // flush 期间可能有新事件追加：只删掉已发送的前 sent 行
                val cur = if (file.exists()) file.readLines().filter { it.isNotBlank() } else emptyList()
                val rest = if (sent >= cur.size) emptyList() else cur.drop(sent)
                rewrite(rest)
            }
        }
        return sent == pending.size
    }

    // ─── 内部 ────────────────────────────────────────────────────────────────

    @Volatile
    private var approxCount = -1

    private fun append(line: String) {
        if (approxCount < 0) {
            approxCount = if (file.exists()) file.readLines().count { it.isNotBlank() } else 0
        }
        file.appendText(line + "\n")
        approxCount++
        // 队列上限 1000 条，超出丢最旧（设计文档 9.2）
        if (approxCount > MAX_QUEUE) {
            rewrite(file.readLines().filter { it.isNotBlank() }.takeLast(MAX_QUEUE))
        }
    }

    private fun rewrite(lines: List<String>) {
        if (lines.isEmpty()) file.delete()
        else file.writeText(lines.joinToString("\n") + "\n")
        approxCount = lines.size
    }

    companion object {
        private const val MAX_QUEUE = 1000
        private const val MAX_BATCH = 100 // protocol.MetricsMaxBatch

        /** 事件白名单——与 server/pkg/protocol/dto.go 的 MetricWhitelist 保持同步。 */
        private val WHITELIST = setOf(
            "asr.first_partial_ms",
            "asr.release_to_commit_ms",
            "audio.start_ms",
            "ws.connect_ms",
            "ws.reconnect",
            "asr.session",
            "inject.fail",
            "onboarding.step",
            "login.success",
            "purchase.success",
            "kb.enabled",
        )

        @Volatile
        private var instance: MetricsQueue? = null

        fun get(context: Context): MetricsQueue =
            instance ?: synchronized(this) {
                instance ?: MetricsQueue(context.applicationContext).also { instance = it }
            }
    }
}
