package com.humand.demo.modules.CallService

import android.content.Context
import android.view.View
import android.view.View.MeasureSpec
import android.widget.FrameLayout
import io.getstream.video.android.core.ParticipantState
import io.getstream.webrtc.android.ui.VideoTextureViewRenderer
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
import com.humand.demo.modules.LivestreamService.LivestreamManager
import org.webrtc.RendererCommon
import org.webrtc.VideoTrack
import stream.video.sfu.models.TrackType

/**
 * Native view that renders a Stream video participant's video track.
 * Uses [VideoTextureViewRenderer] which manages its own TextureView surface and
 * handles Stream's EGL configuration internally.
 */
class CallVideoView(context: Context) : FrameLayout(context) {

    private var renderer: CallVideoTextureViewRenderer? = null
    private var currentVideoTrack: VideoTrack? = null
    private var pendingVideoTrack: VideoTrack? = null
    private var isVisibilitySet: Boolean = false

    private var participantsObservationJob: Job? = null
    private var trackObservationJob: Job? = null
    private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())

    private val measureAndLayout = Runnable {
        if (width > 0 && height > 0) {
            measure(
                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
            )
            layout(left, top, right, bottom)
        }
    }

    var sessionId: String = ""
        set(value) {
            if (field != value) {
                field = value
                updateVideoTrack()
            }
        }

    var trackType: String = CallConstants.TrackType.VIDEO
        set(value) {
            if (field != value) {
                field = value
                updateVideoTrack()
            }
        }

    var isMirrored: Boolean = false
        set(value) {
            field = value
            renderer?.setMirror(value)
        }

    var scalingMode: String = CallConstants.ScalingMode.FILL
        set(value) {
            field = value
            updateScalingMode()
        }

    /** Returns whichever manager currently has an active Stream call. */
    private fun activeManager(): StreamVideoManager? {
        val callMgr = CallManager.getInstanceOrNull()
        if (callMgr?.getCall() != null) return callMgr
        val livestreamMgr = LivestreamManager.getInstanceOrNull()
        if (livestreamMgr?.getCall() != null) return livestreamMgr
        return null
    }

    init {
        setBackgroundColor(android.graphics.Color.BLACK)
    }

    private fun getSfuTrackType(): TrackType {
        return when (trackType) {
            CallConstants.TrackType.SCREEN_SHARE -> TrackType.TRACK_TYPE_SCREEN_SHARE
            else -> TrackType.TRACK_TYPE_VIDEO
        }
    }

    /**
     * Lazily initialize the renderer when the EGL context becomes available.
     */
    private fun ensureRenderer(): CallVideoTextureViewRenderer? {
        if (renderer != null) return renderer

        val call = activeManager()?.getCall() ?: return null

        val textureRenderer = CallVideoTextureViewRenderer(context)
        call.initRenderer(
            videoRenderer = textureRenderer,
            sessionId = sessionId,
            trackType = getSfuTrackType(),
            onRendered = {}
        )
        textureRenderer.isOpaque = false
        textureRenderer.setLayerType(View.LAYER_TYPE_HARDWARE, null)
        textureRenderer.setSurfaceReadyListener {
            maybeAttachPendingTrack()
        }
        addView(textureRenderer, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
        renderer = textureRenderer
        textureRenderer.setMirror(isMirrored)
        updateScalingMode()

        if (width > 0 && height > 0) {
            textureRenderer.measure(
                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
            )
            textureRenderer.layout(0, 0, width, height)
        }
        return textureRenderer
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        val w = right - left
        val h = bottom - top
        if (w > 0 && h > 0) {
            renderer?.let { r ->
                r.measure(
                    MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY)
                )
                r.layout(0, 0, w, h)
            }
        }
    }

    override fun requestLayout() {
        super.requestLayout()
        post(measureAndLayout)
    }

    /**
     * Tell the SFU to start/stop sending this participant's track.
     */
    private fun setTrackVisibility(visible: Boolean) {
        if (sessionId.isEmpty()) return
        val call = activeManager()?.getCall() ?: return

        val trackType = getSfuTrackType()
        call.setVisibility(
            sessionId = sessionId,
            trackType = trackType,
            visible = visible
        )
        isVisibilitySet = visible
    }

    private fun updateVideoTrack() {
        stopEventObservation()
        removeCurrentTrack()

        if (sessionId.isEmpty()) return

        // Subscribe to this participant's track via SFU
        setTrackVisibility(true)

        attemptVideoTrackSetup()
        startEventObservation()
    }

    private fun attemptVideoTrackSetup() {
        val manager = activeManager() ?: return
        val participant = manager.getParticipant(sessionId) ?: return

        val track = getTrackForParticipant(participant)
        if (track != null) {
            setTrack(track)
        }
    }

    private fun startEventObservation() {
        val call = activeManager()?.getCall() ?: return

        participantsObservationJob = scope.launch {
            call.state.participants.collectLatest { participants ->
                val participant = participants.find { it.sessionId == sessionId }
                    ?: call.state.localParticipant.value?.takeIf { it.sessionId == sessionId }

                if (participant != null) {
                    observeParticipantTrack(participant)
                } else {
                    trackObservationJob?.cancel()
                    removeCurrentTrack()
                }
            }
        }
    }

    private fun observeParticipantTrack(participant: ParticipantState) {
        trackObservationJob?.cancel()
        trackObservationJob = scope.launch {
            val trackFlow = when (trackType) {
                CallConstants.TrackType.SCREEN_SHARE -> participant.screenSharingTrack
                else -> participant.videoTrack
            }
            trackFlow.collectLatest { streamTrack ->
                val webrtcTrack = streamTrack?.video
                if (webrtcTrack != currentVideoTrack) {
                    removeCurrentTrack()
                    if (webrtcTrack != null) {
                        setTrack(webrtcTrack)
                    }
                }
            }
        }
    }

    private fun stopEventObservation() {
        participantsObservationJob?.cancel()
        participantsObservationJob = null
        trackObservationJob?.cancel()
        trackObservationJob = null
    }

    private fun getTrackForParticipant(participant: ParticipantState): VideoTrack? {
        return when (trackType) {
            CallConstants.TrackType.SCREEN_SHARE -> participant.screenSharingTrack.value?.video
            else -> participant.videoTrack.value?.video
        }
    }

    private fun setTrack(track: VideoTrack) {
        currentVideoTrack = track
        pendingVideoTrack = track
        maybeAttachPendingTrack()
    }

    private fun maybeAttachPendingTrack() {
        val track = pendingVideoTrack ?: return
        val r = ensureRenderer() ?: return
        val surfaceAvailable = r.isAvailable && r.surfaceTexture != null
        if (!surfaceAvailable) {
            // Wait for surface ready callback
            return
        }
        pendingVideoTrack = null
        r.setSurfaceReadyListener(null)
        track.addSink(r)
        r.resumeVideo()
    }

    private fun removeCurrentTrack() {
        val track = currentVideoTrack ?: return
        renderer?.let {
            track.removeSink(it)
            it.pauseVideo()
        }
        if (pendingVideoTrack == track) {
            pendingVideoTrack = null
        }
        currentVideoTrack = null
    }

    private fun updateScalingMode() {
        val scalingType = when (scalingMode) {
            CallConstants.ScalingMode.FIT -> RendererCommon.ScalingType.SCALE_ASPECT_FIT
            else -> RendererCommon.ScalingType.SCALE_ASPECT_FILL
        }
        renderer?.setScalingType(scalingType)
    }

    private fun cleanup() {
        stopEventObservation()
        removeCurrentTrack()
        if (isVisibilitySet) {
            setTrackVisibility(false)
        }
        renderer?.setSurfaceReadyListener(null)
        renderer = null
        scope.cancel()
    }

    override fun onDetachedFromWindow() {
        cleanup()
        super.onDetachedFromWindow()
    }
}
