package com.seven.ikar.kotlin.feature.call

import android.content.Context
import com.seven.ikar.kotlin.core.model.CallConnectedDto
import com.seven.ikar.kotlin.core.model.CallEndedDto
import com.seven.ikar.kotlin.core.model.CallMediaType
import com.seven.ikar.kotlin.core.model.IncomingCallDto
import com.seven.ikar.kotlin.core.model.OutgoingCallDto
import com.seven.ikar.kotlin.core.model.UserSummaryDto
import com.seven.ikar.kotlin.core.push.IncomingCallNotificationManager
import com.seven.ikar.kotlin.core.realtime.RealtimeService
import com.seven.ikar.kotlin.core.session.AuthorizedSessionManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.webrtc.VideoTrack
import java.time.OffsetDateTime
import java.time.format.DateTimeParseException

enum class AudioCallStage {
    None,
    Incoming,
    Outgoing,
    Connecting,
    Active
}

data class AudioCallSnapshot(
    val callId: String,
    val chatId: String,
    val peer: UserSummaryDto,
    val stage: AudioCallStage,
    val durationText: String,
    val mediaType: CallMediaType,
    val videoRenderState: CallVideoRenderState? = null
)

class CallManager(
    private val realtimeService: RealtimeService,
    private val authorizedSessionManager: AuthorizedSessionManager,
    private val webRtcCallService: AndroidWebRtcCallService,
    private val callTonePlayer: AndroidCallTonePlayer,
    private val appContext: Context
) {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private var durationJob: Job? = null
    private var connectedAtRaw: String? = null

    private val _currentCall = MutableStateFlow<AudioCallSnapshot?>(null)
    val currentCall: StateFlow<AudioCallSnapshot?> = _currentCall.asStateFlow()

    init {
        webRtcCallService.onRenderStateChanged = fun(renderState) {
            val current = _currentCall.value ?: return
            if (current.mediaType != CallMediaType.Video) {
                return
            }

            _currentCall.value = current.copy(videoRenderState = renderState)
        }
        webRtcCallService.onConnectionFailure = {
            scope.launch {
                val current = _currentCall.value ?: return@launch
                runCatching {
                    authorizedSessionManager.executeAuthorized { session ->
                        realtimeService.ensureConnected(session.accessToken)
                        realtimeService.endCall(current.callId)
                    }
                }
                clearCurrentCallInternal()
            }
        }

        scope.launch {
            realtimeService.incomingCall.collect(::handleIncomingCall)
        }
        scope.launch {
            realtimeService.callConnected.collect(::handleCallConnected)
        }
        scope.launch {
            realtimeService.callEnded.collect(::handleCallEnded)
        }
        scope.launch {
            realtimeService.callOffer.collect { offer ->
                val current = _currentCall.value ?: return@collect
                if (current.callId == offer.callId) {
                    runCatching { webRtcCallService.handleRemoteOffer(offer) }
                        .onFailure { clearCurrentCall() }
                }
            }
        }
        scope.launch {
            realtimeService.callAnswer.collect { answer ->
                val current = _currentCall.value ?: return@collect
                if (current.callId == answer.callId) {
                    runCatching { webRtcCallService.handleRemoteAnswer(answer) }
                        .onFailure { clearCurrentCall() }
                }
            }
        }
        scope.launch {
            realtimeService.callIceCandidate.collect { candidate ->
                val current = _currentCall.value ?: return@collect
                if (current.callId == candidate.callId) {
                    runCatching { webRtcCallService.handleRemoteIceCandidate(candidate) }
                        .onFailure { clearCurrentCall() }
                }
            }
        }
    }

    fun canStartCallForChat(chatId: String): Boolean =
        chatId.isNotBlank() && _currentCall.value == null

    fun isCallForChat(chatId: String): Boolean =
        _currentCall.value?.chatId == chatId

    suspend fun startOutgoingAudioCall(chatId: String) {
        startOutgoingCall(chatId, CallMediaType.Audio)
    }

    suspend fun startOutgoingVideoCall(chatId: String) {
        startOutgoingCall(chatId, CallMediaType.Video)
    }

    fun acceptCurrentCall() {
        val current = _currentCall.value ?: return
        if (current.stage != AudioCallStage.Incoming) {
            return
        }

        IncomingCallNotificationManager.cancelIncomingCall(appContext, current.callId)
        _currentCall.value = current.copy(stage = AudioCallStage.Connecting)
        scope.launch {
            runCatching {
                authorizedSessionManager.executeAuthorized { session ->
                    realtimeService.ensureConnected(session.accessToken)
                    realtimeService.acceptCall(current.callId)
                }
            }.onFailure {
                clearCurrentCallInternal()
            }
        }
    }

    fun declineCurrentCall() {
        val current = _currentCall.value ?: return
        IncomingCallNotificationManager.cancelIncomingCall(appContext, current.callId)
        scope.launch {
            runCatching {
                authorizedSessionManager.executeAuthorized { session ->
                    realtimeService.ensureConnected(session.accessToken)
                    realtimeService.declineCall(current.callId)
                }
            }
            clearCurrentCallInternal()
        }
    }

    fun endCurrentCall() {
        val current = _currentCall.value ?: return
        IncomingCallNotificationManager.cancelIncomingCall(appContext, current.callId)
        scope.launch {
            runCatching {
                authorizedSessionManager.executeAuthorized { session ->
                    realtimeService.ensureConnected(session.accessToken)
                    realtimeService.endCall(current.callId)
                }
            }
            clearCurrentCallInternal()
        }
    }

    fun presentIncomingCallInvite(
        callId: String,
        chatId: String,
        callerId: String,
        callerDisplayName: String,
        callerPhoneNumber: String?,
        mediaType: CallMediaType
    ) {
        val current = _currentCall.value
        if (current?.callId == callId || current != null) {
            return
        }

        callTonePlayer.stopOutgoingRingback()
        stopDurationLoop()
        connectedAtRaw = null
        _currentCall.value = AudioCallSnapshot(
            callId = callId,
            chatId = chatId,
            peer = UserSummaryDto(
                id = callerId,
                username = callerDisplayName,
                displayName = callerDisplayName,
                isOnline = false,
                phoneNumber = callerPhoneNumber
            ),
            stage = AudioCallStage.Incoming,
            durationText = "00:00",
            mediaType = mediaType
        )
    }

    fun dismissCallIfMatches(callId: String?) {
        val normalizedCallId = callId?.takeIf { it.isNotBlank() } ?: return
        if (_currentCall.value?.callId == normalizedCallId) {
            clearCurrentCall()
        }
    }

    private suspend fun startOutgoingCall(chatId: String, mediaType: CallMediaType) {
        if (!canStartCallForChat(chatId)) {
            return
        }

        handleOutgoingCallStarted(realtimeService.startCall(chatId, mediaType))
    }

    private fun handleOutgoingCallStarted(call: OutgoingCallDto) {
        callTonePlayer.startOutgoingRingback()
        stopDurationLoop()
        connectedAtRaw = null
        _currentCall.value = AudioCallSnapshot(
            callId = call.callId,
            chatId = call.chatId,
            peer = call.peer,
            stage = AudioCallStage.Outgoing,
            durationText = "00:00",
            mediaType = call.mediaType
        )
    }

    private fun handleIncomingCall(call: IncomingCallDto) {
        if (_currentCall.value != null) {
            return
        }

        callTonePlayer.stopOutgoingRingback()
        stopDurationLoop()
        connectedAtRaw = null
        _currentCall.value = AudioCallSnapshot(
            callId = call.callId,
            chatId = call.chatId,
            peer = call.caller,
            stage = AudioCallStage.Incoming,
            durationText = "00:00",
            mediaType = call.mediaType
        )
    }

    private fun handleCallConnected(call: CallConnectedDto) {
        val current = _currentCall.value ?: return
        if (current.callId != call.callId) {
            return
        }

        callTonePlayer.stopOutgoingRingback()
        IncomingCallNotificationManager.cancelIncomingCall(appContext, call.callId)
        connectedAtRaw = call.connectedAt
        _currentCall.value = current.copy(
            stage = AudioCallStage.Active,
            durationText = "00:00",
            mediaType = call.mediaType
        )
        restartDurationLoop()

        scope.launch {
            runCatching {
                webRtcCallService.start(
                    callId = call.callId,
                    mediaType = call.mediaType,
                    iceServers = call.iceServers,
                    shouldCreateOffer = call.shouldCreateOffer,
                    sendOffer = realtimeService::sendCallOffer,
                    sendAnswer = realtimeService::sendCallAnswer,
                    sendIceCandidate = realtimeService::sendCallIceCandidate
                )
            }.onFailure {
                clearCurrentCallInternal()
            }
        }
    }

    private fun handleCallEnded(call: CallEndedDto) {
        val current = _currentCall.value ?: return
        if (current.callId != call.callId) {
            return
        }

        clearCurrentCall()
    }

    private fun restartDurationLoop() {
        stopDurationLoop()
        val startedAt = parseConnectedAt(connectedAtRaw) ?: return
        durationJob = scope.launch {
            while (true) {
                val current = _currentCall.value
                if (current == null || current.stage != AudioCallStage.Active) {
                    return@launch
                }

                _currentCall.value = current.copy(
                    durationText = formatDurationMillis(
                        System.currentTimeMillis() - startedAt.toInstant().toEpochMilli()
                    )
                )
                delay(1_000)
            }
        }
    }

    private fun stopDurationLoop() {
        durationJob?.cancel()
        durationJob = null
    }

    private fun clearCurrentCall() {
        scope.launch {
            clearCurrentCallInternal()
        }
    }

    private suspend fun clearCurrentCallInternal() {
        stopDurationLoop()
        connectedAtRaw = null
        callTonePlayer.stopOutgoingRingback()
        IncomingCallNotificationManager.cancelIncomingCall(appContext, _currentCall.value?.callId)
        runCatching { webRtcCallService.stop() }
        _currentCall.value = null
    }

    private fun parseConnectedAt(value: String?): OffsetDateTime? {
        if (value.isNullOrBlank()) {
            return null
        }

        return try {
            OffsetDateTime.parse(value)
        } catch (_: DateTimeParseException) {
            null
        }
    }

    private fun formatDurationMillis(durationMillis: Long): String {
        val totalSeconds = (durationMillis.coerceAtLeast(0) / 1_000).toInt()
        val minutes = totalSeconds / 60
        val seconds = totalSeconds % 60
        return "%02d:%02d".format(minutes, seconds)
    }
}
