package com.radiola.service

import android.content.Context
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.extractor.DefaultExtractorsFactory
import com.radiola.domain.model.Recording
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.io.File
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Контроллер воспроизведения записей эфира (отдельный ExoPlayer, независимый от радио).
 */
@Singleton
class RecordingPlaybackController @Inject constructor(
    @ApplicationContext private val context: Context,
    private val playerController: PlayerController
) {
    private val scope = CoroutineScope(Dispatchers.Main)

    private val _current = MutableStateFlow<Recording?>(null)
    val current: StateFlow<Recording?> = _current

    private val _isPlaying = MutableStateFlow(false)
    val isPlaying: StateFlow<Boolean> = _isPlaying

    private val _positionMs = MutableStateFlow(0L)
    val positionMs: StateFlow<Long> = _positionMs

    private val _durationMs = MutableStateFlow(0L)
    val durationMs: StateFlow<Long> = _durationMs

    // Записи эфира — сырые ADTS-AAC/MP3 без контейнера и индексов перемотки.
    // Включаем CBR-seeking, иначе ExoPlayer считает поток неперематываемым
    // (seekTo не работал, запись всегда стартовала с начала).
    private val extractorsFactory = DefaultExtractorsFactory()
        .setConstantBitrateSeekingEnabled(true)
        .setConstantBitrateSeekingAlwaysEnabled(true)

    // Плеер создаём ЛЕНИВО — запись играют редко, а раньше второй ExoPlayer висел
    // в памяти всю сессию у каждого. Освобождаем в stop(): обычно после остановки
    // пользователь уходит с экрана записей, держать декодер/буферы незачем.
    private var exoPlayer: ExoPlayer? = null

    private val playerListener = object : Player.Listener {
        override fun onIsPlayingChanged(playing: Boolean) {
            _isPlaying.value = playing
            // Поллер позиции крутится ТОЛЬКО во время игры — раньше цикл 2 Гц
            // работал всю сессию вхолостую (буст main-loop / батарея).
            if (playing) startPositionPolling() else stopPositionPolling()
        }

        override fun onPlaybackStateChanged(playbackState: Int) {
            when (playbackState) {
                Player.STATE_READY -> {
                    val dur = exoPlayer?.duration ?: C.TIME_UNSET
                    _durationMs.value = if (dur == C.TIME_UNSET) 0L else dur
                }
                Player.STATE_ENDED -> {
                    _isPlaying.value = false
                    _positionMs.value = _durationMs.value
                    stopPositionPolling()
                }
                else -> Unit
            }
        }
    }

    private var positionPollingJob: Job? = null

    private fun ensurePlayer(): ExoPlayer =
        exoPlayer ?: ExoPlayer.Builder(context)
            .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory))
            .build()
            .also { it.addListener(playerListener); exoPlayer = it }

    private fun startPositionPolling() {
        if (positionPollingJob?.isActive == true) return
        positionPollingJob = scope.launch {
            while (isActive) {
                val p = exoPlayer ?: break
                if (p.isPlaying) {
                    _positionMs.value = p.currentPosition
                    val dur = p.duration
                    if (dur != C.TIME_UNSET) _durationMs.value = dur
                }
                delay(500)
            }
        }
    }

    private fun stopPositionPolling() {
        positionPollingJob?.cancel()
        positionPollingJob = null
    }

    /**
     * Начать воспроизведение записи. Сначала останавливает радио.
     */
    fun play(recording: Recording) {
        // Останавливаем радиоплеер
        playerController.pause()
        playerController.stop()

        _current.value = recording
        _positionMs.value = 0L
        _durationMs.value = recording.duration ?: 0L

        val player = ensurePlayer()
        val mediaItem = MediaItem.fromUri(android.net.Uri.fromFile(File(recording.filePath)))
        player.setMediaItem(mediaItem)
        player.prepare()
        player.play()
    }

    /** Переключить паузу/воспроизведение. */
    fun togglePlayPause() {
        val player = exoPlayer ?: return
        if (player.isPlaying) player.pause() else player.play()
    }

    /** Перейти к позиции в мс. */
    fun seekTo(ms: Long) {
        val player = exoPlayer ?: return
        val target = ms.coerceIn(0L, _durationMs.value.coerceAtLeast(1L))
        player.seekTo(target)
        _positionMs.value = target
    }

    /** Перемотать на deltaMs (может быть отрицательным). */
    fun seekBy(deltaMs: Long) {
        seekTo(_positionMs.value + deltaMs)
    }

    /** Остановить воспроизведение, освободить плеер и сбросить текущую запись. */
    fun stop() {
        stopPositionPolling()
        exoPlayer?.release()
        exoPlayer = null
        _current.value = null
        _isPlaying.value = false
        _positionMs.value = 0L
        _durationMs.value = 0L
    }
}
