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

import android.media.MediaPlayer
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.util.concurrent.Semaphore

data class VoiceNotePlaybackEvent(
    val attachmentId: String,
    val isPlaying: Boolean,
    val positionMs: Long,
    val durationMs: Long
)

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

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

    suspend fun toggle(attachmentId: String, localFilePath: String) {
        sync.acquire()
        try {
            if (player != null && currentAttachmentId == attachmentId) {
                stopCurrent()
                return
            }

            stopCurrent()

            val nextPlayer = MediaPlayer().apply {
                setDataSource(localFilePath)
                prepare()
            }

            player = nextPlayer
            currentAttachmentId = attachmentId
            durationMs = nextPlayer.duration.toLong().coerceAtLeast(0L)

            nextPlayer.setOnCompletionListener {
                scope.launch { stopCurrent(playbackCompleted = true) }
            }
            nextPlayer.start()
            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 { mediaPlayer ->
            try {
                if (safeIsPlaying(mediaPlayer)) {
                    mediaPlayer.stop()
                }
            } catch (_: Throwable) {
            }

            mediaPlayer.reset()
            mediaPlayer.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 safeIsPlaying(mediaPlayer: MediaPlayer?): Boolean =
        try {
            mediaPlayer?.isPlaying == true
        } catch (_: Throwable) {
            false
        }

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