package com.ttstd.fluttercontroller

import android.graphics.SurfaceTexture
import android.media.MediaCodec
import android.media.MediaFormat
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.Surface
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.view.TextureRegistry
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import org.webrtc.VideoSink
import org.webrtc.VideoTrack
import com.cloudwebrtc.webrtc.FlutterWebRTCPlugin

private const val CHANNEL = "com.ttstd.fluttercontroller/self_codec"
private const val TAG = "SelfCodecDecoderPlugin"

private const val MAGIC: Byte = 0xAB.toByte()
private const val PROTOCOL_VERSION = 1
private const val HEADER_SIZE = 13
private const val UNIT_TYPE_CONFIG = 1
private const val UNIT_TYPE_FRAME = 2
private const val PLACEHOLDER_W = 1920
private const val PLACEHOLDER_H = 1080
// 解码耗时滑动平均窗口（帧数）。
private const val DECODE_STATS_WINDOW = 90

/// 单个单元的分片重组缓冲。
private class ChunkBuffer(val seq: Int, val total: Int) {
    private val chunks = arrayOfNulls<ByteArray>(total)
    var received = 0
        private set

    fun add(idx: Int, data: ByteArray) {
        if (idx in 0 until total && chunks[idx] == null) {
            chunks[idx] = data
            received++
        }
    }

    fun isComplete(): Boolean = received == total

    fun join(): ByteArray {
        val out = ByteArrayOutputStream()
        for (c in chunks) if (c != null) out.write(c)
        return out.toByteArray()
    }
}

private class Frame(val data: ByteArray, val pts: Long, val key: Boolean)

/// WebRTC 全托管模式下的解码耗时探针：给远端视频轨道额外挂一个 [VideoSink]，
/// 逐帧记录真实到达时间，计算帧间耗时（≈真实解码+渲染周期）的 last/avg/peak 与帧率。
/// 与自编码解码器不同，这里拿不到 MediaCodec 的单帧解码开销，只能测"连续两帧被解码
/// 交付"的真实时间间隔——它是原生层实测值，不会像 totalDecodeTime 那样恒为 0。
private class DecodeProbeSink {
    private val lock = Any()
    private var lastArrivalNs: Long = 0
    private var sumMs = 0.0
    private var count = 0
    private var lastMs = 0.0
    private var avgMs = 0.0
    private var peakMs = 0.0
    private var lastFpsStampMs = 0L
    private var framesSinceFps = 0
    private var fpsNow = 0

    fun onFrame() {
        val now = System.nanoTime()
        synchronized(lock) {
            if (lastArrivalNs != 0L) {
                val deltaMs = (now - lastArrivalNs) / 1_000_000.0
                // 丢弃异常跳变（首帧 / 长时间卡顿后的大间隔），避免污染统计。
                if (deltaMs in 0.5..1000.0) {
                    lastMs = deltaMs
                    if (peakMs < deltaMs) peakMs = deltaMs
                    count++
                    sumMs += deltaMs
                    avgMs = sumMs / count
                    // 1 秒滑动窗口估算当前帧率。
                    val nowMs = System.currentTimeMillis()
                    framesSinceFps++
                    if (lastFpsStampMs != 0L && nowMs - lastFpsStampMs >= 1000) {
                        fpsNow = framesSinceFps * 1000 / (nowMs - lastFpsStampMs).toInt()
                        framesSinceFps = 0
                        lastFpsStampMs = nowMs
                    } else if (lastFpsStampMs == 0L) {
                        lastFpsStampMs = nowMs
                    }
                }
            }
            lastArrivalNs = now
        }
    }

    fun snapshot(): Map<String, Any> = synchronized(lock) {
        mapOf(
            "lastMs" to lastMs,
            "avgMs" to avgMs,
            "peakMs" to peakMs,
            "totalFrames" to count,
            "fpsNow" to fpsNow,
            "fpsAvg" to if (avgMs > 0) (1000.0 / avgMs).toInt() else 0
        )
    }

    fun reset() = synchronized(lock) {
        lastArrivalNs = 0
        sumMs = 0.0
        count = 0
        lastMs = 0.0
        avgMs = 0.0
        peakMs = 0.0
        lastFpsStampMs = 0L
        framesSinceFps = 0
        fpsNow = 0
    }
}

private data class ProbeHolder(
    val track: VideoTrack,
    val sink: VideoSink,
    val stats: DecodeProbeSink
)

/// 自编码解码器（控制端原生实现）。
///
/// 对应 WebRTCController 的 SelfCodecDecoder.java：从 `video` DataChannel 接收
/// 二进制分片，重组为完整单元，使用 Android MediaCodec 硬解 H.264，
/// 并渲染到 Flutter 纹理（TextureRegistry.SurfaceTextureEntry）。
class SelfCodecDecoderPlugin :
    FlutterPlugin, ActivityAware, MethodChannel.MethodCallHandler {

    private var methodChannel: MethodChannel? = null
    private var textureRegistry: TextureRegistry? = null

    /// WebRTC 全托管模式的解码耗时探针（独立通道，复用同一 MethodCallHandler）。
    private var probeChannel: MethodChannel? = null
    private var probeHolder: ProbeHolder? = null

    private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
    private var surfaceTexture: SurfaceTexture? = null
    private var surface: Surface? = null

    private var decoder: MediaCodec? = null
    private var configured = false
    private var enabled = false
    private val configLock = Any()

    private val chunkBuffers = HashMap<Int, ChunkBuffer>()
    private val pendingConfigs = ArrayList<ByteArray>()
    private val frameQueue = ArrayList<Frame>()
    private val inputQueue = ArrayList<Int>()

    // 已完成重组的最大单元序号，用于丢弃明显过期的旧单元。
    private var lastSeq = -1

    // —— 解码耗时统计 ——
    // 记录每帧从 queueInputBuffer 到 onOutputBufferAvailable 的时延（毫秒）。
    private val statsLock = Any()
    private val decodeStartNs = HashMap<Long, Long>() // pts(us) -> 入队 nanoTime
    private val recentDecodeMs = ArrayDeque<Double>()  // 最近若干帧耗时，用于滑动平均
    private var lastDecodeMs = 0.0
    private var maxDecodeMs = 0.0
    private var decodedFrames = 0L

    private val mainHandler = Handler(Looper.getMainLooper())

    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        textureRegistry = binding.textureRegistry
        methodChannel = MethodChannel(binding.binaryMessenger, CHANNEL)
        methodChannel?.setMethodCallHandler(this)
        // 解码耗时探针（WebRTC 全托管模式）：独立通道，复用同一 handler。
        probeChannel = MethodChannel(binding.binaryMessenger, "webrtc_decode_probe")
        probeChannel?.setMethodCallHandler(this)
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        methodChannel?.setMethodCallHandler(null)
        methodChannel = null
        probeChannel?.setMethodCallHandler(null)
        probeChannel = null
        textureRegistry = null
    }

    override fun onAttachedToActivity(binding: ActivityPluginBinding) {}

    override fun onDetachedFromActivityForConfigChanges() {}

    override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}

    override fun onDetachedFromActivity() {}

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        when (call.method) {
            "create" -> {
                try {
                    val id = createInternal()
                    if (id >= 0) result.success(id) else result.error("E_CREATE", "failed", null)
                } catch (e: Exception) {
                    Log.e(TAG, "create error", e)
                    result.error("E_CREATE", e.message, null)
                }
            }
            "feed" -> {
                val bytes = call.arguments as? ByteArray
                if (bytes != null) {
                    try {
                        onBinaryMessage(ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN))
                    } catch (e: Exception) {
                        Log.e(TAG, "feed error", e)
                    }
                }
                result.success(null)
            }
            "setEnabled" -> {
                enabled = call.arguments as? Boolean ?: false
                if (!enabled) releaseDecoder()
                result.success(null)
            }
            "getStats" -> {
                result.success(collectStats())
            }
            "dispose" -> {
                releaseAll()
                result.success(null)
            }
            "attachWebRtcDecodeProbe" -> {
                val trackId = call.argument<String>("trackId")
                if (trackId == null) {
                    result.error("PROBE", "trackId is null", null)
                } else {
                    attachWebRtcDecodeProbe(trackId, result)
                }
            }
            "detachWebRtcDecodeProbe" -> detachWebRtcDecodeProbe(result)
            "getWebRtcDecodeStats" -> getWebRtcDecodeStats(result)
            else -> result.notImplemented()
        }
    }

    // —— WebRTC 全托管模式解码耗时探针 ——

    private fun attachWebRtcDecodeProbe(trackId: String, result: MethodChannel.Result) {
        try {
            val plugin = FlutterWebRTCPlugin.sharedSingleton
            val track = plugin?.getRemoteTrack(trackId)
            if (track == null) {
                result.error("PROBE", "remote video track not found: $trackId", null)
                return
            }
            // getRemoteTrack 返回 org.webrtc.MediaStreamTrack（超类），addSink 只在
            // 子类 org.webrtc.VideoTrack 上，用安全强转拿到 VideoTrack。
            val videoTrack = track as? VideoTrack
            if (videoTrack == null) {
                result.error("PROBE", "track is not a video track: $trackId", null)
                return
            }
            detachWebRtcDecodeProbeInternal()
            val stats = DecodeProbeSink()
            val sink = VideoSink { stats.onFrame() }
            videoTrack.addSink(sink)
            probeHolder = ProbeHolder(videoTrack, sink, stats)
            result.success(null)
        } catch (e: Exception) {
            Log.e(TAG, "attachWebRtcDecodeProbe failed", e)
            result.error("PROBE", e.message, null)
        }
    }

    private fun detachWebRtcDecodeProbeInternal() {
        probeHolder?.let { (track, sink, stats) ->
            try {
                track.removeSink(sink)
            } catch (e: Exception) {
                Log.w(TAG, "removeSink failed", e)
            }
            stats.reset()
        }
        probeHolder = null
    }

    private fun detachWebRtcDecodeProbe(result: MethodChannel.Result) {
        detachWebRtcDecodeProbeInternal()
        result.success(null)
    }

    private fun getWebRtcDecodeStats(result: MethodChannel.Result) {
        val holder = probeHolder
        if (holder == null) {
            result.success(null)
        } else {
            result.success(holder.stats.snapshot())
        }
    }

    private fun createInternal(): Int {
        if (textureEntry != null) return textureEntry!!.id().toInt()
        val entry = textureRegistry?.createSurfaceTexture() ?: return -1
        textureEntry = entry
        val st = entry.surfaceTexture()
        surfaceTexture = st
        st.setDefaultBufferSize(PLACEHOLDER_W, PLACEHOLDER_H)
        // MediaCodec 把解码帧渲染到该 SurfaceTexture。必须消费（updateTexImage）
        // 新帧，否则 SurfaceTexture 一直停留在初始（黑）帧，导致切换自编码后无画面。
        st.setOnFrameAvailableListener { texture: SurfaceTexture ->
            try {
                texture.updateTexImage()
            } catch (e: Exception) {
                Log.w(TAG, "updateTexImage failed", e)
            }
        }
        surface = Surface(st)
        return entry.id().toInt()
    }

    private fun onBinaryMessage(buffer: ByteBuffer) {
        if (!enabled) return
        while (buffer.remaining() >= HEADER_SIZE) {
            val start = buffer.position()
            val magic = buffer.get()
            val seq = buffer.int
            val total = buffer.short.toInt()
            val idx = buffer.short.toInt()
            val len = buffer.int
            if (magic != MAGIC) {
                Log.w(TAG, "bad magic byte: $magic")
                return
            }
            if (len < 0 || buffer.remaining() < len) {
                buffer.position(start)
                break
            }
            val chunkData = ByteArray(len)
            buffer.get(chunkData)
            handleChunk(seq, total, idx, chunkData)
        }
    }

    private fun handleChunk(seq: Int, total: Int, idx: Int, data: ByteArray) {
        if (total <= 0) return
        // 过期检查：同一"单元"的所有分片共用同一个 seq（以 idx 区分），
        // 因此不能用 seq <= lastSeq 丢弃——否则同一单元除首片外的分片都会被误删，
        // 导致大帧永远无法重组、自编码串流黑屏。这里只丢弃明显更旧（seq 明显小于 lastSeq）
        // 的旧单元分片，相同 seq（同单元的其它分片）放行。
        if (lastSeq != -1 && seq < lastSeq && (lastSeq - seq) < 1000) return
        var buf = chunkBuffers[seq]
        if (buf == null) {
            buf = ChunkBuffer(seq, total)
            chunkBuffers[seq] = buf
            // 防止内存堆积：重组缓冲超过 24 个未完成单元时丢弃最旧的。
            if (chunkBuffers.size > 24) {
                val minKey = chunkBuffers.keys.minOrNull()
                if (minKey != null) chunkBuffers.remove(minKey)
            }
        }
        buf.add(idx, data)
        if (buf.isComplete()) {
            chunkBuffers.remove(seq)
            if (seq > lastSeq) lastSeq = seq
            handleUnit(buf.join())
        }
    }

    private fun handleUnit(unit: ByteArray) {
        if (unit.size < 10) return
        val bb = ByteBuffer.wrap(unit).order(ByteOrder.BIG_ENDIAN)
        val type = bb.get().toInt() and 0xFF
        val pts = bb.int.toLong() and 0xFFFFFFFFL
        val isKey = bb.get().toInt() != 0
        val len = bb.int
        if (len < 0 || bb.remaining() < len) return
        val data = ByteArray(len)
        bb.get(data)
        when (type) {
            UNIT_TYPE_CONFIG -> handleConfig(data)
            UNIT_TYPE_FRAME -> handleFrame(data, pts, isKey)
        }
    }

    private fun handleConfig(config: ByteArray) {
        synchronized(configLock) {
            // 去重：仅当队列中尚无相同参数集时加入，避免关键帧前重发造成堆积。
            // 关键：绝不能因为 configured 就 return —— 编码端把 SPS(csd-0) 与 PPS(csd-1)
            // 作为两个独立的 CONFIG 单元分别发送。若第一个 CONFIG(SPS) 到达后即置
            // configured=true 而直接丢弃第二个 CONFIG(PPS)，解码器只拿到 SPS 缺少 PPS，
            // H.264 将无法解码 → 永久黑屏（这正是"自编码无法显示"的根因）。
            for (c in pendingConfigs) {
                if (c.contentEquals(config)) return
            }
            pendingConfigs.add(config)
            if (!configured) {
                tryConfigure()
            } else {
                // 已配置时也尝试喂入，以应对中途的参数集更新。
                feed()
            }
        }
    }

    private fun handleFrame(frame: ByteArray, pts: Long, key: Boolean) {
        synchronized(configLock) {
            frameQueue.add(Frame(frame, pts, key))
            tryConfigure()
            feed()
        }
    }

    private fun tryConfigure() {
        synchronized(configLock) {
            if (configured || surface == null || pendingConfigs.isEmpty()) return
            val format = MediaFormat.createVideoFormat(
                MediaFormat.MIMETYPE_VIDEO_AVC, PLACEHOLDER_W, PLACEHOLDER_H)
            var codec: MediaCodec? = null
            try {
                codec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
                codec.setCallback(object : MediaCodec.Callback() {
                    override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
                        synchronized(configLock) {
                            inputQueue.add(index)
                            feed()
                        }
                    }

                    override fun onOutputBufferAvailable(
                        codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
                        // 用输出帧的 presentationTimeUs 匹配入队时间戳，统计解码耗时。
                        recordDecodeTime(info.presentationTimeUs)
                        try {
                            codec.releaseOutputBuffer(index, true)
                        } catch (e: Exception) {
                            Log.w(TAG, "releaseOutputBuffer failed", e)
                        }
                    }

                    override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
                        Log.e(TAG, "decoder error", e)
                    }

                    override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
                        // getInteger(key, default) 需要 API 29+，用 containsKey 兼容低版本
                        val w = if (format.containsKey(MediaFormat.KEY_WIDTH))
                            format.getInteger(MediaFormat.KEY_WIDTH) else 0
                        val h = if (format.containsKey(MediaFormat.KEY_HEIGHT))
                            format.getInteger(MediaFormat.KEY_HEIGHT) else 0
                        if (w > 0 && h > 0) {
                            surfaceTexture?.setDefaultBufferSize(w, h)
                            mainHandler.post {
                                methodChannel?.invokeMethod(
                                    "onSize", mapOf("width" to w, "height" to h))
                            }
                        }
                    }
                })
                codec.configure(format, surface, null, 0)
                codec.start()
                decoder = codec
                configured = true
                feed()
            } catch (e: Exception) {
                Log.e(TAG, "configure failed", e)
                try {
                    codec?.release()
                } catch (_: Exception) {
                }
                decoder = null
                configured = false
            }
        }
    }

    private fun feed() {
        synchronized(configLock) {
            if (!configured) return
            // 先喂入参数集（SPS / PPS）。
            while (pendingConfigs.isNotEmpty() && inputQueue.isNotEmpty()) {
                val config = pendingConfigs.removeAt(0)
                val index = inputQueue.removeAt(0)
                val inBuf = decoder?.getInputBuffer(index) ?: return
                try {
                    inBuf.clear()
                    inBuf.put(config)
                    decoder?.queueInputBuffer(
                        index, 0, config.size, 0, MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
                } catch (e: Exception) {
                    Log.w(TAG, "queue config failed", e)
                }
            }
            // 再喂入视频帧。
            while (frameQueue.isNotEmpty() && inputQueue.isNotEmpty()) {
                val frame = frameQueue.removeAt(0)
                val index = inputQueue.removeAt(0)
                val inBuf = decoder?.getInputBuffer(index) ?: return
                try {
                    inBuf.clear()
                    inBuf.put(frame.data)
                    val flags = if (frame.key) MediaCodec.BUFFER_FLAG_KEY_FRAME else 0
                    decoder?.queueInputBuffer(
                        index, 0, frame.data.size, frame.pts, flags)
                    // 记录该帧入队时间，供输出回调计算解码耗时。
                    synchronized(statsLock) {
                        decodeStartNs[frame.pts] = System.nanoTime()
                        // 防止乱序/丢帧导致 map 泄漏。
                        if (decodeStartNs.size > 300) decodeStartNs.clear()
                    }
                } catch (e: Exception) {
                    Log.w(TAG, "queue frame failed", e)
                }
            }
        }
    }

    /// 输出帧到达时计算解码耗时（毫秒），并更新滑动平均/峰值/计数。
    private fun recordDecodeTime(pts: Long) {
        synchronized(statsLock) {
            val startNs = decodeStartNs.remove(pts) ?: return
            val ms = (System.nanoTime() - startNs) / 1_000_000.0
            lastDecodeMs = ms
            if (ms > maxDecodeMs) maxDecodeMs = ms
            decodedFrames++
            recentDecodeMs.addLast(ms)
            while (recentDecodeMs.size > DECODE_STATS_WINDOW) recentDecodeMs.removeFirst()
        }
    }

    /// 汇总解码统计供 Dart 侧读取（毫秒）。
    private fun collectStats(): Map<String, Any> {
        synchronized(statsLock) {
            val avg = if (recentDecodeMs.isEmpty()) 0.0
                else recentDecodeMs.sum() / recentDecodeMs.size
            return mapOf(
                "lastMs" to lastDecodeMs,
                "avgMs" to avg,
                "maxMs" to maxDecodeMs,
                "count" to decodedFrames
            )
        }
    }

    private fun releaseDecoder() {
        synchronized(configLock) {
            try {
                decoder?.stop()
            } catch (_: Exception) {
            }
            try {
                decoder?.release()
            } catch (_: Exception) {
            }
            decoder = null
            configured = false
            pendingConfigs.clear()
            frameQueue.clear()
            inputQueue.clear()
            // 重置序号并清空重组缓冲，避免重连/重启后首帧被误判为过期而丢弃。
            lastSeq = -1
            chunkBuffers.clear()
            // 重置解码耗时统计。
            synchronized(statsLock) {
                decodeStartNs.clear()
                recentDecodeMs.clear()
                lastDecodeMs = 0.0
                maxDecodeMs = 0.0
                decodedFrames = 0L
            }
        }
    }

    private fun releaseAll() {
        releaseDecoder()
        try {
            surface?.release()
        } catch (_: Exception) {
        }
        surface = null
        try {
            surfaceTexture?.release()
        } catch (_: Exception) {
        }
        surfaceTexture = null
        try {
            textureEntry?.release()
        } catch (_: Exception) {
        }
        textureEntry = null
        chunkBuffers.clear()
        enabled = false
    }
}
