package com.seven.ikar.kotlin.feature.chat

import android.content.Context
import android.net.Uri
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import java.io.File
import java.util.concurrent.Semaphore

data class VoiceNotePlaybackEvent(
    val attachmentId: String,
    val isPlaying: Boolean,
    val positionMs: Long,
    val durationMs: Long,
    val errorMessage: String? = null
)

class AndroidVoiceNotePlayerService(
    private val context: Context
) {
    private val sync = Semaphore(1)
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
    private val _events = MutableSharedFlow<VoiceNotePlaybackEvent>(extraBufferCapacity = 32)
    val events: SharedFlow<VoiceNotePlaybackEvent> = _events.asSharedFlow()

    private var player: ExoPlayer? = null
    private var currentAttachmentId: String? = null
    private var durationMs: Long = 0L
    private var tickerJob: Job? = null

    suspend fun toggle(attachmentId: String, localFilePath: String, pauseExternalAudio: Boolean = false) {
        toggleMedia(
            attachmentId = attachmentId,
            mediaItem = MediaItem.fromUri(Uri.fromFile(File(localFilePath))),
            pauseExternalAudio = pauseExternalAudio
        )
    }

    suspend fun toggleUri(attachmentId: String, uri: Uri, pauseExternalAudio: Boolean = false) {
        toggleMedia(
            attachmentId = attachmentId,
            mediaItem = MediaItem.fromUri(uri),
            pauseExternalAudio = pauseExternalAudio
        )
    }

    private suspend fun toggleMedia(
        attachmentId: String,
        mediaItem: MediaItem,
        pauseExternalAudio: Boolean
    ) {
        sync.acquire()
        try {
            if (player != null && currentAttachmentId == attachmentId) {
                stopCurrent()
                return
            }

            stopCurrent()

            val nextPlayer = ExoPlayer.Builder(context).build().apply {
                setAudioAttributes(VoiceNoteAudioAttributes, pauseExternalAudio)
            }
            player = nextPlayer
            currentAttachmentId = attachmentId
            durationMs = 0L

            nextPlayer.addListener(object : Player.Listener {
                override fun onPlaybackStateChanged(playbackState: Int) {
                    val activeAttachmentId = currentAttachmentId ?: return
                    durationMs = safeDuration(nextPlayer)
                    when (playbackState) {
                        Player.STATE_READY -> emitEvent(
                            attachmentId = activeAttachmentId,
                            isPlaying = safeIsPlaying(nextPlayer),
                            positionMs = safePosition(nextPlayer),
                            durationMs = durationMs
                        )

                        Player.STATE_ENDED -> scope.launch { stopCurrent(playbackCompleted = true) }
                    }
                }

                override fun onPlayerError(error: PlaybackException) {
                    val failedAttachmentId = currentAttachmentId ?: return
                    scope.launch {
                        stopCurrent()
                        emitEvent(
                            attachmentId = failedAttachmentId,
                            isPlaying = false,
                            positionMs = 0L,
                            durationMs = 0L,
                            errorMessage = "Не удалось воспроизвести голосовое сообщение."
                        )
                    }
                }
            })
            nextPlayer.setMediaItem(mediaItem)
            nextPlayer.prepare()
            nextPlayer.play()
            startTicker()
            emitEvent(attachmentId, true, 0L, durationMs)
        } finally {
            sync.release()
        }
    }

    suspend fun stop() {
        sync.acquire()
        try {
            stopCurrent()
        } finally {
            sync.release()
        }
    }

    private suspend fun stopCurrent(playbackCompleted: Boolean = false) {
        val existingPlayer = player
        val attachmentId = currentAttachmentId
        val completedDuration = durationMs
        val finalPosition = if (playbackCompleted) completedDuration else safePosition(existingPlayer)

        player = null
        currentAttachmentId = null
        durationMs = 0L
        stopTicker()

        existingPlayer?.let { exoPlayer ->
            exoPlayer.release()
        }

        if (attachmentId != null) {
            emitEvent(attachmentId, false, finalPosition, completedDuration)
        }
    }

    private fun startTicker() {
        stopTicker()
        tickerJob = scope.launch {
            try {
                while (true) {
                    delay(250)
                    val activePlayer = player ?: break
                    val attachmentId = currentAttachmentId ?: break
                    emitEvent(
                        attachmentId = attachmentId,
                        isPlaying = safeIsPlaying(activePlayer),
                        positionMs = safePosition(activePlayer),
                        durationMs = durationMs
                    )
                }
            } catch (_: CancellationException) {
            }
        }
    }

    private fun stopTicker() {
        tickerJob?.cancel()
        tickerJob = null
    }

    private fun emitEvent(attachmentId: String, isPlaying: Boolean, positionMs: Long, durationMs: Long) {
        _events.tryEmit(
            VoiceNotePlaybackEvent(
                attachmentId = attachmentId,
                isPlaying = isPlaying,
                positionMs = positionMs.coerceAtLeast(0L),
                durationMs = durationMs.coerceAtLeast(0L)
            )
        )
    }

    private fun emitEvent(
        attachmentId: String,
        isPlaying: Boolean,
        positionMs: Long,
        durationMs: Long,
        errorMessage: String?
    ) {
        _events.tryEmit(
            VoiceNotePlaybackEvent(
                attachmentId = attachmentId,
                isPlaying = isPlaying,
                positionMs = positionMs.coerceAtLeast(0L),
                durationMs = durationMs.coerceAtLeast(0L),
                errorMessage = errorMessage
            )
        )
    }

    private fun safeIsPlaying(exoPlayer: ExoPlayer?): Boolean =
        try {
            exoPlayer?.isPlaying == true
        } catch (_: Throwable) {
            false
        }

    private fun safePosition(exoPlayer: ExoPlayer?): Long =
        try {
            exoPlayer?.currentPosition?.coerceAtLeast(0L) ?: 0L
        } catch (_: Throwable) {
            0L
        }

    private fun safeDuration(exoPlayer: ExoPlayer?): Long =
        try {
            exoPlayer?.duration?.takeIf { it != C.TIME_UNSET }?.coerceAtLeast(0L) ?: 0L
        } catch (_: Throwable) {
            0L
        }

    private companion object {
        val VoiceNoteAudioAttributes: AudioAttributes = AudioAttributes.Builder()
            .setUsage(C.USAGE_MEDIA)
            .setContentType(C.AUDIO_CONTENT_TYPE_SPEECH)
            .build()
    }
}
