package com.humand.demo.modules.LivestreamService

import android.content.Context
import android.media.AudioAttributes
import com.humand.demo.modules.Audio.AudioSessionHelper
import com.humand.demo.modules.Audio.AudioRouteCoordinator
import com.humand.demo.modules.CallService.*
import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest

/**
 * Livestream coordinator for host and viewer functionality.
 */
class LivestreamManager private constructor(context: Context) : StreamVideoManager(context) {

    companion object {
        @Volatile
        private var instance: LivestreamManager? = null

        fun getInstance(context: Context): LivestreamManager {
            return instance ?: synchronized(this) {
                instance ?: LivestreamManager(context.applicationContext).also { instance = it }
            }
        }

        fun getInstanceOrNull(): LivestreamManager? = instance
    }

    private var isHost: Boolean = false
    private var livestreamId: String? = null
    private var providerCallId: String? = null
    private var isHls: Boolean = false

    // Viewers only receive audio — use MODE_NORMAL (STREAM_MUSIC) so volume can reach 0.
    // Hosts send+receive audio via WebRTC and need MODE_IN_COMMUNICATION.
    override val usesCommunicationAudioMode: Boolean
        get() = isHost

    // Closed caption observation (for viewers only)
    private var closedCaptionObservationJob: Job? = null

    override fun buildCallServiceConfigRegistry(): CallServiceConfigRegistry {
        return CallServiceConfigRegistry().apply {
            update(CallConstants.CallType.LIVESTREAM) {
                setRunCallServiceInForeground(false)
                // Viewers only receive audio — use USAGE_MEDIA so Android routes through
                // STREAM_MUSIC (volume can reach 0, no call icon, proper media routing
                // to external displays like Chromecast/HDMI).
                // Hosts need USAGE_VOICE_COMMUNICATION for WebRTC send/receive.
                if (!isHost) {
                    setAudioUsage(AudioAttributes.USAGE_MEDIA)
                }
            }
        }
    }

    override fun logMessage(message: String) {
        livestreamLog(message)
    }

    private fun hasActiveLivestream(): Boolean {
        return streamCall != null || currentCallingState == CallConstants.CallingState.JOINING
    }

    private fun updateLivestreamState(callState: String, livestreamState: String) {
        currentCallingState = callState
        emitStateChanged(livestreamState)
    }

    override fun emitStateChanged(state: String) {
        RNLivestreamManager.emitStateChanged(state)
    }

    override fun emitParticipantsChanged(participants: List<NativeParticipant>) {
        val totalCount = streamCall?.state?.participantCounts?.value?.total ?: participants.size
        RNLivestreamManager.emitParticipantsChanged(participants, totalCount)
    }

    override fun emitMediaStateChanged(microphoneEnabled: Boolean, cameraEnabled: Boolean, speakerEnabled: Boolean) {
        RNLivestreamManager.emitMediaStateChanged(microphoneEnabled, cameraEnabled, speakerEnabled)
    }

    override fun onCallEndedByRemote(endReason: String) {
        logMessage("Livestream ended by host (reason: $endReason)")
        managerScope.launch {
            cleanup()
        }
        RNLivestreamManager.emitStateChanged(LivestreamConstants.LivestreamState.LEFT)
    }

    fun startHostLivestream(id: String, providerCallId: String, cameraEnabled: Boolean) {
        logMessage("Starting host livestream: $id")

        // Block if a call is active - only one audio context allowed at a time
        if (CallManager.getInstance(context).hasActiveCall()) {
            logMessage("startHostLivestream: Call is active, cannot start livestream")
            emitError("Cannot start livestream while in a call")
            return
        }

        if (hasActiveLivestream()) {
            logMessage("startHostLivestream: Already active or joining, ignoring")
            return
        }

        this.livestreamId = id
        this.providerCallId = providerCallId
        this.isHost = true
        this.isHls = false

        managerScope.launch {
            joinLivestream(cameraEnabled = cameraEnabled, isHls = false)
        }
    }

    fun joinViewerLivestream(id: String, providerCallId: String, isHls: Boolean) {
        logMessage("Joining viewer livestream: $id, isHls: $isHls")

        // Block if a call is active - only one audio context allowed at a time
        if (CallManager.getInstance(context).hasActiveCall()) {
            logMessage("joinViewerLivestream: Call is active, cannot join livestream")
            emitError("Cannot join livestream while in a call")
            return
        }

        if (hasActiveLivestream()) {
            logMessage("joinViewerLivestream: Already active or joining, ignoring")
            return
        }

        this.livestreamId = id
        this.providerCallId = providerCallId
        this.isHost = false
        this.isHls = isHls

        managerScope.launch {
            joinLivestream(cameraEnabled = false, isHls = isHls)
        }
    }

    private suspend fun joinLivestream(cameraEnabled: Boolean, isHls: Boolean) {
        try {
            updateLivestreamState(
                CallConstants.CallingState.JOINING,
                LivestreamConstants.LivestreamState.JOINING
            )

            // Get livestream token from backend
            val tokenResponse = CallApiService.getLivestreamToken(isHost)
            if (tokenResponse == null) {
                logMessage("Failed to get livestream token")
                emitError("Failed to get livestream token")
                cleanup()
                return
            }

            val apiKey = CallApiService.streamApiKey ?: ""
            val video = ensureStreamClient(
                apiKey = apiKey,
                userId = tokenResponse.participantId,
                token = tokenResponse.token,
                tokenRefreshHandler = { CallApiService.getLivestreamToken(isHost)?.token }
            )

            // Create and join the livestream call
            val callId = providerCallId ?: return
            val call = video.call(CallConstants.CallType.LIVESTREAM, callId)
            streamCall = call

            // Get call info
            withRetry { call.get() }

            // Configure audio BEFORE joining based on role
            // Host: MODE_IN_COMMUNICATION for sending/receiving audio
            // Viewer: MODE_NORMAL with USAGE_MEDIA for receiving only (better audio quality)
            if (isHost) {
                AudioSessionHelper.getInstance(context).activateForWebRTC()
            } else if (!isHls) {
                AudioSessionHelper.getInstance(context).configureForSpeaker()
            }
            AudioRouteCoordinator.setLivestreamRoute(context, active = true, wantsSpeaker = true)

            // Join the call
            withRetry { call.join(create = isHost) }

            logMessage("Joined livestream successfully")
            updateLivestreamState(
                CallConstants.CallingState.JOINED,
                LivestreamConstants.LivestreamState.JOINED
            )

            // Start state observation
            startStateObservation()

            // Enable media after join completes
            if (isHost) {
                enableHostMedia(cameraEnabled)
            } else {
                configureViewerAudio(isHls)
            }

        } catch (e: Exception) {
            logMessage("Failed to join livestream: ${e.message}")
            emitError("Failed to join livestream: ${e.message}")
            cleanup()
        }
    }

    private suspend fun enableHostMedia(cameraEnabled: Boolean) {
        logMessage("Enabling host media, camera: $cameraEnabled")

        try {
            setMicrophoneEnabled(true)
            setCameraEnabled(cameraEnabled)
            setSpeakerEnabled(true)

            emitCurrentMediaState()
        } catch (e: Exception) {
            logMessage("Error enabling host media: ${e.message}")
        }
    }

    private suspend fun configureViewerAudio(isHls: Boolean) {
        logMessage("Configuring viewer audio, isHls: $isHls")

        try {
            if (!isHls) {
                // Switch WebRTC's AudioTrack to USAGE_MEDIA so Android routes through
                // STREAM_MUSIC instead of STREAM_VOICE_CALL. This removes the call icon
                // from the volume slider, allows volume to reach 0, and enables proper
                // media routing to external displays (Chromecast/HDMI).
                val call = streamCall
                if (call != null) {
                    val success = call.speaker.setAudioUsage(AudioAttributes.USAGE_MEDIA)
                    logMessage("setAudioUsage(USAGE_MEDIA) result: $success")
                }

                AudioSessionHelper.getInstance(context).configureForSpeaker()
                setSpeakerEnabled(true)
            }

            // Start closed caption observation for viewers
            startClosedCaptionObservation()
        } catch (e: Exception) {
            logMessage("Error configuring viewer audio: ${e.message}")
        }
    }

    private fun startClosedCaptionObservation() {
        val call = streamCall ?: return

        closedCaptionObservationJob?.cancel()
        closedCaptionObservationJob = managerScope.launch {
            call.state.closedCaptions.collectLatest { captions ->
                captions.lastOrNull()?.let { caption ->
                    RNLivestreamManager.emitClosedCaption(
                        caption.text,
                        caption.speakerId ?: ""
                    )
                }
            }
        }
        logMessage("Started closed caption observation for viewer")
    }

    fun stopLivestream() {
        logMessage("Stopping livestream")

        managerScope.launch {
            try {
                if (isHost) {
                    streamCall?.stopLive()
                    streamCall?.end()
                }
                cleanup()
                emitStateChanged(LivestreamConstants.LivestreamState.LEFT)
            } catch (e: Exception) {
                logMessage("Error stopping livestream: ${e.message}")
            }
        }
    }

    fun leaveLivestream() {
        logMessage("Leaving livestream")

        managerScope.launch {
            leaveLivestreamInternal()
        }
    }

    /**
     * Suspend version of leaveLivestream that can be awaited.
     * Use this when you need to wait for cleanup to complete before proceeding.
     * Emits idle state (user left, broadcast may still be ongoing).
     */
    suspend fun leaveLivestreamAsync() {
        logMessage("leaveLivestreamAsync called")
        leaveLivestreamInternal()
    }

    private suspend fun leaveLivestreamInternal() {
        cleanup()
        // Emit exited (user left, broadcast still ongoing) not left (broadcast ended)
        emitStateChanged(LivestreamConstants.LivestreamState.EXITED)
    }

    private fun emitError(message: String) {
        RNLivestreamManager.emitError(message)
    }

    private suspend fun cleanup() {
        logMessage("Cleaning up livestream")

        closedCaptionObservationJob?.cancel()
        closedCaptionObservationJob = null

        cleanupStreamClient()
        // Remove livestream client from SDK so CallManager can create a fresh one
        disconnectStreamVideo()
        AudioRouteCoordinator.setLivestreamRoute(context, active = false, wantsSpeaker = false)
        // Deactivate WebRTC audio session
        AudioSessionHelper.getInstance(context).deactivateWebRTC()

        livestreamId = null
        providerCallId = null
        isHost = false
        isHls = false

        currentCallingState = CallConstants.CallingState.IDLE
    }

    fun setMicrophoneEnabledFromRN(enabled: Boolean) {
        managerScope.launch {
            setMicrophoneEnabled(enabled)
        }
    }

    fun setCameraEnabledFromRN(enabled: Boolean) {
        managerScope.launch {
            setCameraEnabled(enabled)
        }
    }

    fun switchCameraFromRN() {
        managerScope.launch {
            switchCamera()
        }
    }
}
