package com.omixlab.lckcontrol.streaming

import android.graphics.Bitmap
import android.hardware.HardwareBuffer
import android.util.Log
import android.view.Surface
import com.omixlab.lckcontrol.shared.StreamPlan
import com.omixlab.lckcontrol.shared.StreamingConfig
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.nio.ByteBuffer
import javax.inject.Inject
import javax.inject.Singleton

enum class StreamingState {
    IDLE, STARTING, LIVE, STOPPING, ERROR
}

/**
 * High-level streaming lifecycle manager.
 * Bridges stream plan configuration to the native streaming engine.
 * Stream keys and RTMP URLs stay within the app process — never exposed via AIDL.
 */
@Singleton
class StreamingManager @Inject constructor() {

    companion object {
        private const val TAG = "StreamingManager"
    }

    private var engine: NativeStreamingEngine? = null
    private var texturePoolBuffers: Array<HardwareBuffer>? = null
    private var texturePoolWidth: Int = 0
    private var texturePoolHeight: Int = 0

    private val _state = MutableStateFlow(StreamingState.IDLE)
    val state: StateFlow<StreamingState> = _state.asStateFlow()

    private val _stats = MutableStateFlow(StreamingStats())
    val stats: StateFlow<StreamingStats> = _stats.asStateFlow()

    private val _error = MutableStateFlow<String?>(null)
    val error: StateFlow<String?> = _error.asStateFlow()

    /**
     * Start streaming for a plan with APP_STREAMING execution mode.
     * RTMP URLs are constructed internally from the plan's destinations.
     */
    fun startStreaming(plan: StreamPlan, config: StreamingConfig, width: Int, height: Int) {
        if (_state.value != StreamingState.IDLE) {
            Log.w(TAG, "Cannot start streaming, current state: ${_state.value}")
            return
        }

        val destinations = plan.destinations.filter {
            it.rtmpUrl.isNotBlank() && it.streamKey.isNotBlank()
        }

        if (destinations.isEmpty()) {
            _error.value = "No destinations with RTMP credentials"
            _state.value = StreamingState.ERROR
            return
        }

        // Use texture pool dimensions if available, otherwise use caller-provided defaults
        val actualWidth = if (texturePoolWidth > 0) texturePoolWidth else width
        val actualHeight = if (texturePoolHeight > 0) texturePoolHeight else height
        Log.d(TAG, "Starting streaming at ${actualWidth}x${actualHeight} (pool=${texturePoolWidth}x${texturePoolHeight}, requested=${width}x${height})")

        _state.value = StreamingState.STARTING
        _error.value = null

        try {
            val eng = NativeStreamingEngine()
            eng.create(
                width = actualWidth,
                height = actualHeight,
                videoBitrate = config.videoBitrate,
                audioBitrate = config.audioBitrate,
                sampleRate = config.audioSampleRate,
                channels = config.audioChannels,
                keyframeInterval = config.keyFrameInterval,
            )

            // Add RTMP destinations — stream keys stay in-process
            for (dest in destinations) {
                val fullUrl = "${dest.rtmpUrl}/${dest.streamKey}"
                eng.addDestination(fullUrl)
                Log.d(TAG, "Added destination: ${dest.service}")
            }

            eng.onStats = { stats ->
                _stats.value = stats
            }

            eng.onError = { code, message ->
                Log.e(TAG, "Streaming error $code: $message")
                _error.value = message
                _state.value = StreamingState.ERROR
            }

            eng.onBufferReleased = { index ->
                onBufferReleased?.invoke(index)
            }

            if (eng.start()) {
                engine = eng
                _state.value = StreamingState.LIVE
                Log.i(TAG, "Streaming started with ${destinations.size} destinations")
            } else {
                eng.destroy()
                _error.value = "Failed to start streaming engine"
                _state.value = StreamingState.ERROR
            }
        } catch (e: Throwable) {
            Log.e(TAG, "Failed to start streaming", e)
            _error.value = e.message ?: "Unknown error"
            _state.value = StreamingState.ERROR
        }
    }

    /**
     * Register texture pool buffers from the game.
     * Buffers are stored for reference — the native engine receives individual
     * buffers via submitVideoFrame.
     */
    fun registerTexturePool(buffers: Array<HardwareBuffer>, width: Int, height: Int, format: Int) {
        texturePoolBuffers = buffers
        texturePoolWidth = width
        texturePoolHeight = height
        Log.d(TAG, "Texture pool registered: ${buffers.size} buffers, ${width}x${height}")
    }

    fun unregisterTexturePool() {
        texturePoolBuffers = null
        texturePoolWidth = 0
        texturePoolHeight = 0
        Log.d(TAG, "Texture pool unregistered")
    }

    private var videoFrameCount = 0

    /** Callback when a buffer is released after processing. */
    var onBufferReleased: ((Int) -> Unit)? = null

    /** Forward a video frame from the game to the native engine. */
    fun submitVideoFrame(bufferIndex: Int, timestampNs: Long, fenceFd: Int) {
        val buffers = texturePoolBuffers
        if (buffers == null) {
            if (videoFrameCount++ % 30 == 0) Log.w(TAG, "submitVideoFrame: no texture pool")
            return
        }
        if (bufferIndex < 0 || bufferIndex >= buffers.size) {
            if (videoFrameCount++ % 30 == 0) Log.w(TAG, "submitVideoFrame: index $bufferIndex out of range [0,${buffers.size})")
            return
        }
        val eng = engine
        if (eng == null) {
            if (videoFrameCount++ % 30 == 0) Log.w(TAG, "submitVideoFrame: engine is null (state=${_state.value})")
            return
        }
        eng.submitVideoFrame(buffers[bufferIndex], timestampNs, fenceFd, bufferIndex)
        if (++videoFrameCount % 30 == 0) {
            Log.d(TAG, "submitVideoFrame: forwarded frame #$videoFrameCount idx=$bufferIndex")
        }
    }

    /** Forward audio PCM from the game to the native engine. */
    fun submitAudioFrame(pcmData: ByteArray, timestampNs: Long) {
        engine?.submitAudioFrame(pcmData, timestampNs)
    }

    /** Stop streaming and release all resources. */
    fun stopStreaming() {
        Log.w(TAG, "stopStreaming() called from state=${_state.value}", Exception("Caller trace"))
        if (_state.value != StreamingState.LIVE && _state.value != StreamingState.ERROR) {
            return
        }

        _state.value = StreamingState.STOPPING

        engine?.let { eng ->
            eng.stop()
            eng.destroy()
        }
        engine = null

        _state.value = StreamingState.IDLE
        _stats.value = StreamingStats()
        Log.i(TAG, "Streaming stopped")
    }

    fun isStreaming(): Boolean = _state.value == StreamingState.LIVE

    // --- Preview surface ---

    fun setPreviewSurface(surface: Surface) {
        engine?.setPreviewSurface(surface)
    }

    fun removePreviewSurface() {
        engine?.removePreviewSurface()
    }

    // --- Composition layers ---

    fun addCompositionLayer(
        bitmap: Bitmap,
        posX: Float, posY: Float,
        scaleX: Float, scaleY: Float,
        rotation: Float, opacity: Float,
        zOrder: Int, tag: String,
    ): Int {
        val rgba = bitmapToRgba(bitmap)
        return engine?.addCompositionLayer(
            rgba, bitmap.width, bitmap.height,
            posX, posY, scaleX, scaleY, rotation, opacity, zOrder, tag,
        ) ?: -1
    }

    fun removeCompositionLayer(layerId: Int) {
        engine?.removeCompositionLayer(layerId)
    }

    fun updateCompositionLayerTransform(
        layerId: Int, posX: Float, posY: Float,
        scaleX: Float, scaleY: Float, rotation: Float,
    ) {
        engine?.updateCompositionLayerTransform(layerId, posX, posY, scaleX, scaleY, rotation)
    }

    fun updateCompositionLayerOpacity(layerId: Int, opacity: Float) {
        engine?.updateCompositionLayerOpacity(layerId, opacity)
    }

    fun setCompositionLayerEnabled(layerId: Int, enabled: Boolean) {
        engine?.setCompositionLayerEnabled(layerId, enabled)
    }

    private fun bitmapToRgba(bitmap: Bitmap): ByteArray {
        val argbBitmap = if (bitmap.config != Bitmap.Config.ARGB_8888) {
            bitmap.copy(Bitmap.Config.ARGB_8888, false)
        } else {
            bitmap
        }
        val buffer = ByteBuffer.allocate(argbBitmap.byteCount)
        argbBitmap.copyPixelsToBuffer(buffer)
        if (argbBitmap !== bitmap) argbBitmap.recycle()
        return buffer.array()
    }
}
