package com.matchlivetv.match_live_tv

import android.content.Context
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import androidx.annotation.RequiresApi
import com.pedro.common.ConnectChecker
import com.pedro.library.generic.GenericStream
import com.pedro.library.util.BitrateAdapter
import com.pedro.library.view.OpenGlView
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger

data class StreamingConfig(
    val rtmpUrl: String,
    val width: Int = 1280,
    val height: Int = 720,
    val videoBitrate: Int = 2_500_000,
    val audioBitrate: Int = 128_000,
    val fps: Int = 30,
    val rotation: Int = 0,
    val sampleRate: Int = 44_100,
    val stereo: Boolean = true,
    val maxReconnectAttempts: Int = 10,
    val reconnectDelayMs: Long = 5_000L,
)

data class StreamingMetrics(
    val isStreaming: Boolean,
    val isPreviewActive: Boolean,
    val isConnected: Boolean,
    val state: String,
    val bitrateKbps: Long,
    val fps: Int,
    val durationMs: Long,
    val reconnectAttempts: Int,
    val lastError: String?,
) {
    fun toMap(): Map<String, Any?> = mapOf(
        "isStreaming" to isStreaming,
        "isPreviewActive" to isPreviewActive,
        "isConnected" to isConnected,
        "state" to state,
        "bitrateKbps" to bitrateKbps,
        "fps" to fps,
        "durationMs" to durationMs,
        "reconnectAttempts" to reconnectAttempts,
        "lastError" to lastError,
    )
}

enum class StreamingState {
    IDLE,
    PREVIEWING,
    CONNECTING,
    STREAMING,
    RECONNECTING,
    STOPPING,
    ERROR,
}

interface StreamingEngineListener {
    fun onStateChanged(state: StreamingState, message: String? = null)
    fun onMetricsUpdated(metrics: StreamingMetrics)
    fun onError(message: String)
}

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class StreamingEngine(
    private val appContext: Context,
) : ConnectChecker {

    private val mainHandler = Handler(Looper.getMainLooper())
    private val listeners = mutableSetOf<StreamingEngineListener>()

    private var genericStream: GenericStream? = null
    private var previewSurface: OpenGlView? = null

    private var config: StreamingConfig? = null
    private var state: StreamingState = StreamingState.IDLE
    private var streamStartedAtMs: Long = 0L
    private var currentBitrateKbps: Long = 0L
    private var currentFps: Int = 0
    private var lastError: String? = null
    private var lastVideoFrameCount: Long = 0L
    private var lastFpsSampleAtMs: Long = 0L
    private val reconnectAttempts = AtomicInteger(0)
    private val isReleased = AtomicBoolean(false)

    private val bitrateAdapter = BitrateAdapter { adaptedBitrate ->
        genericStream?.setVideoBitrateOnFly(adaptedBitrate)
    }

    private val metricsRunnable = object : Runnable {
        override fun run() {
            emitMetrics()
            if (state == StreamingState.STREAMING ||
                state == StreamingState.CONNECTING ||
                state == StreamingState.RECONNECTING ||
                state == StreamingState.PREVIEWING
            ) {
                mainHandler.postDelayed(this, METRICS_INTERVAL_MS)
            }
        }
    }

    fun addListener(listener: StreamingEngineListener) {
        listeners.add(listener)
    }

    fun removeListener(listener: StreamingEngineListener) {
        listeners.remove(listener)
    }

    fun bindStreamPreview(glView: OpenGlView) {
        ensureMainThread()
        val surfaceChanged = previewSurface != null && previewSurface !== glView
        previewSurface = glView

        if (surfaceChanged) {
            genericStream?.let { stream ->
                if (stream.isOnPreview) {
                    stream.stopPreview()
                }
            }
        }

        if (state == StreamingState.STREAMING ||
            state == StreamingState.CONNECTING ||
            state == StreamingState.RECONNECTING
        ) {
            attachPreviewToStream(forceRebind = surfaceChanged)
            return
        }

        val cfg = config ?: previewConfig()
        val stream = genericStream ?: obtainGenericStream(cfg)
        genericStream = stream

        if (!stream.isOnPreview) {
            stopPreviewAndStreamForPrepare(stream)
            val prepared = try {
                stream.prepareVideo(
                    cfg.width,
                    cfg.height,
                    cfg.videoBitrate,
                    cfg.fps,
                    rotation = cfg.rotation,
                ) && stream.prepareAudio(
                    cfg.sampleRate,
                    cfg.stereo,
                    cfg.audioBitrate,
                )
            } catch (_: Exception) {
                false
            }
            if (!prepared) {
                notifyError("Impossibile preparare la preview camera")
                return
            }
        }

        attachPreviewToStream()
        updateState(StreamingState.PREVIEWING)
        startMetricsLoop()
    }

    fun unbindStreamPreview() {
        ensureMainThread()
        previewSurface = null
        genericStream?.let { stream ->
            if (stream.isOnPreview) {
                stream.stopPreview()
            }
        }
        if (state == StreamingState.PREVIEWING) {
            updateState(StreamingState.IDLE)
        }
    }

    fun startStream(streamConfig: StreamingConfig) {
        ensureMainThread()
        if (isReleased.get()) {
            notifyError("StreamingEngine rilasciato")
            return
        }
        if (state == StreamingState.STREAMING ||
            state == StreamingState.CONNECTING ||
            state == StreamingState.RECONNECTING
        ) {
            return
        }

        config = streamConfig
        lastError = null
        reconnectAttempts.set(0)

        val stream = genericStream ?: obtainGenericStream(streamConfig)
        genericStream = stream
        stopPreviewAndStreamForPrepare(stream)

        val prepared = try {
            stream.prepareVideo(
                streamConfig.width,
                streamConfig.height,
                streamConfig.videoBitrate,
                streamConfig.fps,
                rotation = streamConfig.rotation,
            ) && stream.prepareAudio(
                streamConfig.sampleRate,
                streamConfig.stereo,
                streamConfig.audioBitrate,
            )
        } catch (exception: IllegalArgumentException) {
            false
        } catch (exception: IllegalStateException) {
            false
        }

        if (!prepared) {
            notifyError("Configurazione audio/video non valida")
            updateState(StreamingState.ERROR, "prepare failed")
            return
        }

        attachPreviewToStream()

        stream.getStreamClient().setReTries(streamConfig.maxReconnectAttempts)
        bitrateAdapter.setMaxBitrate(streamConfig.videoBitrate + streamConfig.audioBitrate)
        streamStartedAtMs = SystemClock.elapsedRealtime()
        updateState(StreamingState.CONNECTING)
        stream.startStream(streamConfig.rtmpUrl)
        startMetricsLoop()
    }

    fun stopStream() {
        ensureMainThread()
        if (state == StreamingState.IDLE || state == StreamingState.STOPPING) {
            return
        }

        updateState(StreamingState.STOPPING)
        stopMetricsLoop()

        genericStream?.let { stream ->
            if (stream.isStreaming) {
                stream.stopStream()
            }
            if (stream.isOnPreview) {
                stream.stopPreview()
            }
            stream.release()
        }
        genericStream = null

        streamStartedAtMs = 0L
        currentBitrateKbps = 0L
        currentFps = 0
        reconnectAttempts.set(0)
        updateState(StreamingState.IDLE)
        emitMetrics()
    }

    fun release() {
        ensureMainThread()
        if (!isReleased.compareAndSet(false, true)) {
            return
        }
        stopStream()
        unbindStreamPreview()
        listeners.clear()
    }

    fun getMetrics(): StreamingMetrics = buildMetrics()

    override fun onConnectionStarted(url: String) {
        postMain {
            updateState(StreamingState.CONNECTING, url)
        }
    }

    override fun onConnectionSuccess() {
        postMain {
            reconnectAttempts.set(0)
            lastError = null
            updateState(StreamingState.STREAMING)
        }
    }

    override fun onConnectionFailed(reason: String) {
        postMain {
            lastError = reason
            val stream = genericStream ?: return@postMain
            val currentConfig = config ?: return@postMain

            updateState(StreamingState.RECONNECTING, reason)
            val attempt = reconnectAttempts.incrementAndGet()
            val retryScheduled = stream.getStreamClient().reTry(
                currentConfig.reconnectDelayMs,
                reason,
                null,
            )

            if (retryScheduled) {
                emitMetrics()
            } else {
                updateState(StreamingState.ERROR, reason)
                notifyError("Connessione RTMP fallita: $reason")
                stream.stopStream()
            }
        }
    }

    override fun onNewBitrate(bitrate: Long) {
        postMain {
            currentBitrateKbps = bitrate / 1000
            genericStream?.let { stream ->
                bitrateAdapter.adaptBitrate(
                    bitrate,
                    stream.getStreamClient().hasCongestion(),
                )
            }
            emitMetrics()
        }
    }

    override fun onDisconnect() {
        postMain {
            if (state == StreamingState.STREAMING || state == StreamingState.RECONNECTING) {
                updateState(StreamingState.RECONNECTING, "disconnected")
            }
        }
    }

    override fun onAuthError() {
        postMain {
            lastError = "auth_error"
            updateState(StreamingState.ERROR, "auth_error")
            genericStream?.stopStream()
            notifyError("Autenticazione RTMP fallita")
        }
    }

    override fun onAuthSuccess() {
        postMain {
            updateState(StreamingState.STREAMING)
        }
    }

    private fun stopPreviewAndStreamForPrepare(stream: GenericStream) {
        if (stream.isStreaming) {
            stream.stopStream()
        }
        if (stream.isOnPreview) {
            stream.stopPreview()
        }
    }

    private fun attachPreviewToStream(forceRebind: Boolean = false) {
        val glView = previewSurface ?: StreamPreviewHolder.openGlView ?: return
        previewSurface = glView
        val stream = genericStream ?: return
        if (forceRebind && stream.isOnPreview) {
            stream.stopPreview()
        }
        if (!stream.isOnPreview) {
            stream.startPreview(glView, autoHandle = true)
        }
    }

    private fun previewConfig(): StreamingConfig {
        return config ?: StreamingConfig(
            rtmpUrl = "rtmp://127.0.0.1/preview",
            width = 1280,
            height = 720,
        )
    }

    private fun obtainGenericStream(streamConfig: StreamingConfig): GenericStream {
        return GenericStream(
            appContext,
            this,
        ).apply {
            getGlInterface().autoHandleOrientation = true
            getStreamClient().setBitrateExponentialFactor(0.5f)
            getStreamClient().setReTries(streamConfig.maxReconnectAttempts)
        }
    }

    private fun buildMetrics(): StreamingMetrics {
        val durationMs = if (streamStartedAtMs > 0L) {
            SystemClock.elapsedRealtime() - streamStartedAtMs
        } else {
            0L
        }

        return StreamingMetrics(
            isStreaming = state == StreamingState.STREAMING ||
                state == StreamingState.RECONNECTING ||
                state == StreamingState.CONNECTING,
            isPreviewActive = state == StreamingState.PREVIEWING ||
                (genericStream?.isOnPreview == true),
            isConnected = state == StreamingState.STREAMING,
            state = state.name.lowercase(),
            bitrateKbps = currentBitrateKbps,
            fps = currentFps,
            durationMs = durationMs,
            reconnectAttempts = reconnectAttempts.get(),
            lastError = lastError,
        )
    }

    private fun updateState(newState: StreamingState, message: String? = null) {
        state = newState
        listeners.forEach { it.onStateChanged(newState, message) }
        emitMetrics()
    }

    private fun emitMetrics() {
        updateFpsEstimate()
        val metrics = buildMetrics()
        listeners.forEach { it.onMetricsUpdated(metrics) }
    }

    private fun updateFpsEstimate() {
        val stream = genericStream ?: return
        val now = SystemClock.elapsedRealtime()
        val sentFrames = stream.getStreamClient().getSentVideoFrames()
        if (lastFpsSampleAtMs == 0L) {
            lastFpsSampleAtMs = now
            lastVideoFrameCount = sentFrames
            return
        }

        val elapsedMs = now - lastFpsSampleAtMs
        if (elapsedMs >= 1_000L) {
            val frameDelta = sentFrames - lastVideoFrameCount
            currentFps = ((frameDelta * 1000L) / elapsedMs).toInt()
            lastVideoFrameCount = sentFrames
            lastFpsSampleAtMs = now
        }
    }

    private fun notifyError(message: String) {
        listeners.forEach { it.onError(message) }
    }

    private fun startMetricsLoop() {
        mainHandler.removeCallbacks(metricsRunnable)
        mainHandler.post(metricsRunnable)
    }

    private fun stopMetricsLoop() {
        mainHandler.removeCallbacks(metricsRunnable)
    }

    private fun postMain(block: () -> Unit) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            block()
        } else {
            mainHandler.post(block)
        }
    }

    private fun ensureMainThread() {
        check(Looper.myLooper() == Looper.getMainLooper()) {
            "StreamingEngine must be used on the main thread"
        }
    }

    companion object {
        private const val METRICS_INTERVAL_MS = 1_000L
    }
}
