package com.omixlab.lckcontrol.streaming

import android.hardware.HardwareBuffer
import android.util.Log
import android.view.Surface

/**
 * Thin JNI wrapper around the C++ StreamingEngine.
 * All encoding, muxing, and RTMP streaming happens in native code (zero-copy pipeline).
 */
class NativeStreamingEngine {

    companion object {
        private const val TAG = "NativeStreamingEngine"

        init {
            System.loadLibrary("lck_streaming")
        }
    }

    private var nativePtr: Long = 0

    var onStats: ((StreamingStats) -> Unit)? = null
    var onError: ((Int, String) -> Unit)? = null
    var onBufferReleased: ((Int) -> Unit)? = null

    fun create(
        width: Int,
        height: Int,
        videoBitrate: Int,
        audioBitrate: Int,
        sampleRate: Int,
        channels: Int,
        keyframeInterval: Int,
    ) {
        if (nativePtr != 0L) {
            Log.w(TAG, "Engine already created, destroying first")
            destroy()
        }
        nativePtr = nativeCreate(width, height, videoBitrate, audioBitrate,
            sampleRate, channels, keyframeInterval)
    }

    fun addDestination(rtmpUrl: String): Int {
        check(nativePtr != 0L) { "Engine not created" }
        return nativeAddDestination(nativePtr, rtmpUrl)
    }

    fun start(): Boolean {
        check(nativePtr != 0L) { "Engine not created" }
        return nativeStart(nativePtr)
    }

    fun submitVideoFrame(hardwareBuffer: HardwareBuffer, timestampNs: Long, fenceFd: Int, bufferIndex: Int) {
        if (nativePtr == 0L) return
        nativeSubmitVideoFrame(nativePtr, hardwareBuffer, timestampNs, fenceFd, bufferIndex)
    }

    fun submitAudioFrame(pcmData: ByteArray, timestampNs: Long) {
        if (nativePtr == 0L) return
        nativeSubmitAudioFrame(nativePtr, pcmData, timestampNs)
    }

    fun stop() {
        if (nativePtr == 0L) return
        nativeStop(nativePtr)
    }

    fun destroy() {
        if (nativePtr != 0L) {
            nativeDestroy(nativePtr)
            nativePtr = 0
        }
    }

    fun isRunning(): Boolean {
        if (nativePtr == 0L) return false
        return nativeIsRunning(nativePtr)
    }

    // Preview surface
    fun setPreviewSurface(surface: Surface) {
        if (nativePtr == 0L) return
        nativeSetPreviewSurface(nativePtr, surface)
    }

    fun removePreviewSurface() {
        if (nativePtr == 0L) return
        nativeRemovePreviewSurface(nativePtr)
    }

    // Composition layers
    fun addCompositionLayer(
        rgbaData: ByteArray, w: Int, h: Int,
        posX: Float, posY: Float, scaleX: Float, scaleY: Float,
        rotation: Float, opacity: Float, zOrder: Int, tag: String,
    ): Int {
        if (nativePtr == 0L) return -1
        return nativeAddCompositionLayer(nativePtr, rgbaData, w, h,
            posX, posY, scaleX, scaleY, rotation, opacity, zOrder, tag)
    }

    fun removeCompositionLayer(layerId: Int) {
        if (nativePtr == 0L) return
        nativeRemoveCompositionLayer(nativePtr, layerId)
    }

    fun updateCompositionLayerTransform(
        layerId: Int, posX: Float, posY: Float,
        scaleX: Float, scaleY: Float, rotation: Float,
    ) {
        if (nativePtr == 0L) return
        nativeUpdateCompositionLayerTransform(nativePtr, layerId,
            posX, posY, scaleX, scaleY, rotation)
    }

    fun updateCompositionLayerOpacity(layerId: Int, opacity: Float) {
        if (nativePtr == 0L) return
        nativeUpdateCompositionLayerOpacity(nativePtr, layerId, opacity)
    }

    fun setCompositionLayerEnabled(layerId: Int, enabled: Boolean) {
        if (nativePtr == 0L) return
        nativeSetCompositionLayerEnabled(nativePtr, layerId, enabled)
    }

    // Called from native code (JNI callbacks)
    @Suppress("unused")
    private fun onNativeStats(videoBitrate: Long, audioBitrate: Long, fps: Int, droppedFrames: Int) {
        onStats?.invoke(StreamingStats(videoBitrate, audioBitrate, fps, droppedFrames))
    }

    @Suppress("unused")
    private fun onNativeError(code: Int, message: String) {
        Log.e(TAG, "Native error $code: $message")
        onError?.invoke(code, message)
    }

    @Suppress("unused")
    private fun onNativeBufferReleased(bufferIndex: Int) {
        onBufferReleased?.invoke(bufferIndex)
    }

    // Native methods
    private external fun nativeCreate(
        width: Int, height: Int,
        videoBitrate: Int, audioBitrate: Int,
        sampleRate: Int, channels: Int,
        keyframeInterval: Int,
    ): Long

    private external fun nativeAddDestination(ptr: Long, rtmpUrl: String): Int
    private external fun nativeStart(ptr: Long): Boolean
    private external fun nativeSubmitVideoFrame(ptr: Long, hardwareBuffer: HardwareBuffer, timestampNs: Long, fenceFd: Int, bufferIndex: Int)
    private external fun nativeSubmitAudioFrame(ptr: Long, pcmData: ByteArray, timestampNs: Long)
    private external fun nativeStop(ptr: Long)
    private external fun nativeDestroy(ptr: Long)
    private external fun nativeIsRunning(ptr: Long): Boolean

    // Preview surface
    private external fun nativeSetPreviewSurface(ptr: Long, surface: Surface)
    private external fun nativeRemovePreviewSurface(ptr: Long)

    // Composition layers
    private external fun nativeAddCompositionLayer(
        ptr: Long, rgbaData: ByteArray, w: Int, h: Int,
        posX: Float, posY: Float, scaleX: Float, scaleY: Float,
        rotation: Float, opacity: Float, zOrder: Int, tag: String,
    ): Int
    private external fun nativeRemoveCompositionLayer(ptr: Long, layerId: Int)
    private external fun nativeUpdateCompositionLayerTransform(
        ptr: Long, layerId: Int, posX: Float, posY: Float,
        scaleX: Float, scaleY: Float, rotation: Float,
    )
    private external fun nativeUpdateCompositionLayerOpacity(ptr: Long, layerId: Int, opacity: Float)
    private external fun nativeSetCompositionLayerEnabled(ptr: Long, layerId: Int, enabled: Boolean)
}
