package com.radiola.service

import android.content.Context
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.net.Uri
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.ForwardingPlayer
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Metadata
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import android.util.Log
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.audio.AudioSink
import androidx.media3.exoplayer.audio.DefaultAudioSink
import androidx.media3.exoplayer.audio.TeeAudioProcessor
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.extractor.metadata.icy.IcyInfo
import android.os.SystemClock
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton

@UnstableApi
@Singleton
class PlayerController @Inject constructor(
    @ApplicationContext context: Context,
    private val sleepSoundPlayer: SleepSoundPlayer,
    private val audioEffects: AudioEffectsController
) {
    // Анализатор спектра реального звука — для «живого» эквалайзера.
    private val spectrumAnalyzer = AudioSpectrumAnalyzer()
    val spectrum: StateFlow<FloatArray> = spectrumAnalyzer.spectrum

    // RenderersFactory, который вставляет наш tee-процессор в аудио-конвейер
    // (читает декодированный PCM, не меняя звук).
    private val renderersFactory = object : DefaultRenderersFactory(context) {
        override fun buildAudioSink(
            context: Context,
            enableFloatOutput: Boolean,
            enableAudioTrackPlaybackParams: Boolean,
        ): AudioSink {
            return DefaultAudioSink.Builder(context)
                .setEnableFloatOutput(enableFloatOutput)
                .setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
                .setAudioProcessors(arrayOf(TeeAudioProcessor(spectrumAnalyzer)))
                .build()
        }
    }
    private val _isPlaying = MutableStateFlow(false)
    val isPlaying: StateFlow<Boolean> = _isPlaying

    private val _currentStationPrefix = MutableStateFlow<String?>(null)
    val currentStationPrefix: StateFlow<String?> = _currentStationPrefix

    // Id играющей станции — для подсветки активной карточки в списке.
    private val _currentStationId = MutableStateFlow<Int?>(null)
    val currentStationId: StateFlow<Int?> = _currentStationId

    private val _icyTitle = MutableStateFlow<String?>(null)
    val icyTitle: StateFlow<String?> = _icyTitle.asStateFlow()

    // ── Таймер сна ──
    // Оставшееся время в мс (null = таймер выключен). В последние FADE_MS звук
    // плавно затухает (экспоненциальная кривая), затем пауза.
    private val _sleepRemainingMs = MutableStateFlow<Long?>(null)
    val sleepRemainingMs: StateFlow<Long?> = _sleepRemainingMs.asStateFlow()
    private val timerScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
    private var sleepJob: Job? = null

    // Переподключение при обрыве потока (дорога/туннели).
    private var retryCount = 0
    private var reconnectJob: Job? = null

    private companion object {
        const val FADE_MS = 20_000L // длительность плавного затухания в конце таймера
        const val CROSSFADE_MS = 90_000L // переход радио → звук для сна (внутри аутро)
        const val SOUND_VOL = 0.6f // комфортная громкость шума
        const val SOUND_OUTRO_MS = 180_000L // финальное окно со звуком сна (последние ~3 мин)
    }

    private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager

    var onSkipToNext: (() -> Unit)? = null
    var onSkipToPrevious: (() -> Unit)? = null

    private val audioDeviceCallback = object : AudioDeviceCallback() {
        override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
            val addedPlayback = addedDevices?.any { it.isPlaybackDevice() } == true
            if (addedPlayback && _currentStationPrefix.value != null && !exoPlayer.isPlaying) {
                Log.d("PlayerController", "Playback device connected → resume")
                exoPlayer.play()
            }
        }

        override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
            val removedPlayback = removedDevices?.any { it.isPlaybackDevice() } == true
            if (removedPlayback && exoPlayer.isPlaying) {
                Log.d("PlayerController", "Playback device removed → pause")
                exoPlayer.pause()
            }
        }
    }

    private fun AudioDeviceInfo.isPlaybackDevice(): Boolean {
        return isSink && (
            type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
            type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES ||
            type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
            type == AudioDeviceInfo.TYPE_USB_HEADSET ||
            type == AudioDeviceInfo.TYPE_USB_DEVICE ||
            type == AudioDeviceInfo.TYPE_BLE_HEADSET ||
            type == AudioDeviceInfo.TYPE_BLE_SPEAKER
        )
    }

    // HTTP-источник с разрешёнными кросс-протокольными редиректами (http→https):
    // многие станции отдают 301 c http на https, без этого ExoPlayer их не играет.
    private val mediaSourceFactory = DefaultMediaSourceFactory(
        DefaultDataSource.Factory(
            context,
            DefaultHttpDataSource.Factory()
                .setAllowCrossProtocolRedirects(true)
        )
    )

    private val exoPlayer: ExoPlayer = ExoPlayer.Builder(context)
        .setRenderersFactory(renderersFactory)
        .setMediaSourceFactory(mediaSourceFactory)
        .setAudioAttributes(
            AudioAttributes.Builder()
                .setUsage(C.USAGE_MEDIA)
                .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
                .build(),
            true
        )
        .setHandleAudioBecomingNoisy(true)
        // Держим CPU + Wi-Fi активными, пока играем (partial wakelock + wifilock).
        // Без этого при выключенном экране система усыпляет сеть → буфер пустеет →
        // радио глохнет (главная причина «обрыва» в машине по Bluetooth).
        .setWakeMode(C.WAKE_MODE_NETWORK)
        .build()
        .apply {
            addListener(object : Player.Listener {
                override fun onIsPlayingChanged(playing: Boolean) {
                    _isPlaying.value = playing
                    // Успешно играем — сбрасываем счётчик попыток переподключения.
                    if (playing) retryCount = 0
                }

                override fun onPlayerError(error: PlaybackException) {
                    // В дороге сигнал рвётся (туннели, край соты). Не глушим радио
                    // навсегда — пере-готовим поток с нарастающей задержкой.
                    Log.w("PlayerController", "Ошибка плеера: ${error.errorCodeName}, переподключение")
                    scheduleReconnect()
                }

                override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
                    val title = mediaMetadata.title?.toString()
                    if (!title.isNullOrBlank()) {
                        Log.d("PlayerController", "MediaMetadata title: $title")
                        _icyTitle.value = title
                    }
                }

                override fun onMetadata(metadata: Metadata) {
                    Log.d("PlayerController", "onMetadata called, length=${metadata.length()}")
                    for (i in 0 until metadata.length()) {
                        val entry = metadata.get(i)
                        Log.d("PlayerController", "Metadata entry[$i]: ${entry::class.java.simpleName}")
                        when (entry) {
                            is IcyInfo -> {
                                Log.d("PlayerController", "IcyInfo title='${entry.title}', url='${entry.url}', raw='${entry.rawMetadata}'")
                                entry.title?.let {
                                    if (it.isNotBlank()) {
                                        _icyTitle.value = it
                                    }
                                }
                            }
                        }
                    }
                }
            })
            // Фиксированная аудиосессия → эффекты (эквалайзер и т.д.) держатся на ней
            // и переживают смену станций. Привязываем их сразу после создания плеера.
            val sessionId = audioManager.generateAudioSessionId()
            runCatching { setAudioSessionId(sessionId) }
            audioEffects.attach(sessionId)
        }

    /**
     * Переподключение после ошибки потока с нарастающей задержкой (2с→15с, до 10
     * попыток ≈ пережить туннель). Счётчик сбрасывается, как только снова заиграло.
     */
    private fun scheduleReconnect() {
        reconnectJob?.cancel()
        if (retryCount >= 10) return
        val delayMs = (2_000L * (retryCount + 1)).coerceAtMost(15_000L)
        retryCount++
        reconnectJob = timerScope.launch {
            delay(delayMs)
            runCatching {
                exoPlayer.prepare()
                exoPlayer.playWhenReady = true
            }
        }
    }

    val player: Player = object : ForwardingPlayer(exoPlayer) {
        override fun getAvailableCommands(): Player.Commands {
            return super.getAvailableCommands()
                .buildUpon()
                .add(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .add(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build()
        }

        override fun seekToNextMediaItem() {
            onSkipToNext?.invoke() ?: super.seekToNextMediaItem()
        }

        override fun seekToPreviousMediaItem() {
            onSkipToPrevious?.invoke() ?: super.seekToPreviousMediaItem()
        }
    }

    init {
        audioManager.registerAudioDeviceCallback(audioDeviceCallback, null)
    }

    fun play(url: String, stationPrefix: String, stationName: String, stationId: Int? = null) {
        Log.d("PlayerController", "play() called with url=$url prefix=$stationPrefix")
        // Новая станция — сбрасываем переподключение предыдущего потока.
        reconnectJob?.cancel()
        retryCount = 0
        _currentStationId.value = stationId
        _icyTitle.value = null
        val mediaItem = MediaItem.Builder()
            .setUri(url)
            .setMediaMetadata(
                MediaMetadata.Builder()
                    .setTitle(stationName)
                    .setArtist("")
                    .build()
            )
            .build()
        exoPlayer.setMediaItem(mediaItem)
        exoPlayer.prepare()
        exoPlayer.play()
        _currentStationPrefix.value = stationPrefix
    }

    /** Сменить URL потока (переключение качества) без потери текущих метаданных/обложки. */
    fun changeStream(url: String) {
        Log.d("PlayerController", "changeStream() url=$url")
        val keepMetadata = exoPlayer.currentMediaItem?.mediaMetadata
        _icyTitle.value = null
        val builder = MediaItem.Builder().setUri(url)
        if (keepMetadata != null) builder.setMediaMetadata(keepMetadata)
        exoPlayer.setMediaItem(builder.build())
        exoPlayer.prepare()
        exoPlayer.play()
    }

    fun updateMetadata(song: String, artist: String, coverUrl: String, stationName: String) {
        val currentMediaItem = exoPlayer.currentMediaItem ?: return
        val artworkUri = coverUrl.takeIf { it.isNotBlank() }?.let { Uri.parse(it) }
        val updatedMediaItem = currentMediaItem.buildUpon()
            .setMediaMetadata(
                MediaMetadata.Builder()
                    .setTitle(song)
                    .setArtist("$artist  •  $stationName")
                    .setAlbumTitle(stationName)
                    .setArtworkUri(artworkUri)
                    .build()
            )
            .build()
        exoPlayer.replaceMediaItem(0, updatedMediaItem)
    }

    /**
     * Запустить таймер сна на [durationMs] мс.
     * Без [sound]: в последние FADE_MS радио экспоненциально затухает, затем пауза.
     * Со [sound]: радио играет почти весь таймер; в последние SOUND_OUTRO_MS (не больше
     * половины таймера) включается звук для сна — радио кроссфейдится в шум (радио ↓,
     * шум ↑), шум держится, в самом конце затухает в тишину. Засыпаешь под радио, а не
     * под резкий белый шум в первые же полторы минуты.
     */
    fun startSleepTimer(durationMs: Long, sound: SleepSound? = null) {
        sleepJob?.cancel()
        exoPlayer.volume = 1f
        sleepSoundPlayer.stop()
        sleepJob = timerScope.launch {
            val start = SystemClock.elapsedRealtime()
            val end = start + durationMs
            // Финальное окно со звуком — не длиннее половины таймера (для коротких).
            val outro = if (sound != null) SOUND_OUTRO_MS.coerceAtMost(durationMs / 2) else 0L
            // Кроссфейд радио→шум занимает первую половину аутро.
            val crossfade = CROSSFADE_MS.coerceAtMost(outro / 2).coerceAtLeast(1L)
            var soundStarted = false
            while (true) {
                val now = SystemClock.elapsedRealtime()
                val remaining = end - now
                if (remaining <= 0L) break
                _sleepRemainingMs.value = remaining
                when {
                    sound != null && remaining <= outro -> {
                        // Генератор шума стартуем лениво — только в аутро, не весь таймер.
                        if (!soundStarted) {
                            sleepSoundPlayer.start(sound)
                            soundStarted = true
                        }
                        val outroElapsed = outro - remaining
                        when {
                            outroElapsed < crossfade -> {
                                // Кроссфейд: радио вниз, шум вверх.
                                val f = outroElapsed.toFloat() / crossfade
                                exoPlayer.volume = (1f - f).coerceIn(0f, 1f)
                                sleepSoundPlayer.setVolume(f * SOUND_VOL)
                            }
                            remaining <= FADE_MS -> {
                                // Финальное затухание шума в тишину.
                                val frac = remaining.toFloat() / FADE_MS
                                sleepSoundPlayer.setVolume((frac * frac) * SOUND_VOL)
                            }
                            else -> {
                                // Радио отыграло — пауза, шум на комфортной громкости.
                                if (exoPlayer.isPlaying) exoPlayer.pause()
                                sleepSoundPlayer.setVolume(SOUND_VOL)
                            }
                        }
                        delay(150)
                    }
                    sound == null && remaining <= FADE_MS -> {
                        // Без звука: экспоненциальное затухание радио в конце.
                        val frac = remaining.toFloat() / FADE_MS
                        exoPlayer.volume = (frac * frac).coerceIn(0f, 1f)
                        delay(150)
                    }
                    else -> {
                        // Основная фаза: радио играет как обычно.
                        delay(1_000)
                    }
                }
            }
            if (exoPlayer.isPlaying) exoPlayer.pause()
            exoPlayer.volume = 1f
            sleepSoundPlayer.stop()
            _sleepRemainingMs.value = null
            sleepJob = null
        }
    }

    /**
     * Запуск воспроизведения станции по будильнику: играет [url] и плавно нарастает
     * громкость 0 → 1 за [fadeInMs] (мягкое пробуждение).
     */
    fun startAlarmPlayback(
        url: String,
        prefix: String,
        name: String,
        id: Int?,
        fadeInMs: Long = 60_000L,
    ) {
        cancelSleepTimer()
        play(url, prefix, name, id)
        exoPlayer.volume = 0f
        sleepJob?.cancel()
        sleepJob = timerScope.launch {
            val start = SystemClock.elapsedRealtime()
            while (true) {
                val elapsed = SystemClock.elapsedRealtime() - start
                if (elapsed >= fadeInMs) break
                exoPlayer.volume = (elapsed.toFloat() / fadeInMs).coerceIn(0f, 1f)
                delay(200)
            }
            exoPlayer.volume = 1f
            sleepJob = null
        }
    }

    /** Отменить таймер сна, вернуть громкость и заглушить звук сна. */
    fun cancelSleepTimer() {
        sleepJob?.cancel()
        sleepJob = null
        exoPlayer.volume = 1f
        sleepSoundPlayer.stop()
        _sleepRemainingMs.value = null
    }

    /** Включить/выключить расчёт спектра (FFT) — только пока открыт плеер. */
    fun setSpectrumActive(active: Boolean) {
        spectrumAnalyzer.active = active
    }

    fun pause() {
        // Пауза пользователем — отменяем отложенное переподключение, иначе оно
        // позже само возобновит воспроизведение.
        reconnectJob?.cancel()
        exoPlayer.pause()
    }

    fun play() {
        exoPlayer.play()
    }

    fun stop() {
        reconnectJob?.cancel()
        exoPlayer.stop()
        _currentStationPrefix.value = null
        _currentStationId.value = null
    }

    fun release() {
        timerScope.cancel()
        audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
        sleepSoundPlayer.stop()
        exoPlayer.release()
    }
}
