package com.aletheia.app.ui.reader.tts

import android.content.Context
import android.media.AudioAttributes
import android.speech.tts.TextToSpeech
import android.speech.tts.UtteranceProgressListener
import com.aletheia.app.ui.reader.ReaderSpeechPage
import com.aletheia.app.ui.reader.ReaderWebController
import java.util.Locale
import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/**
 * Reads book pages through an installed Russian voice that explicitly declares
 * that it does not require a network connection.
 *
 * The controller deliberately refuses network-only voices. A project-owned
 * neural model can replace this engine behind the same reader-page contract.
 */
class ReaderSpeechController(
    context: Context,
    private val scope: CoroutineScope,
    private val reader: ReaderWebController,
    private val onStateChanged: (State) -> Unit,
    private val onMessage: (String) -> Unit
) {
    enum class State { IDLE, INITIALIZING, PLAYING, PAUSED, ERROR }

    private val appContext = context.applicationContext
    private val preferences = appContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
    private var tts: TextToSpeech? = null
    private var state = State.IDLE
    private var initializationComplete = false
    private var resumeAfterInitialization = false
    private var playbackJob: Job? = null
    private var currentPage: ReaderSpeechPage? = null
    private var lastSpokenSignature: String? = null

    val rate: Float
        get() = preferences.getFloat(KEY_RATE, DEFAULT_RATE).coerceIn(MIN_RATE, MAX_RATE)

    val pitch: Float
        get() = preferences.getFloat(KEY_PITCH, DEFAULT_PITCH).coerceIn(MIN_PITCH, MAX_PITCH)

    fun toggle() {
        when (state) {
            State.PLAYING, State.INITIALIZING -> pause()
            State.IDLE, State.PAUSED, State.ERROR -> start()
        }
    }

    fun start() {
        if (!initializationComplete) {
            resumeAfterInitialization = true
            initialize()
            return
        }
        val resumePausedPage = state == State.PAUSED
        updateState(State.PLAYING)
        speakCurrentPage(reusePausedPage = resumePausedPage)
    }

    fun pause() {
        resumeAfterInitialization = false
        playbackJob?.cancel()
        playbackJob = null
        tts?.stop()
        updateState(State.PAUSED)
    }

    fun stop() {
        resumeAfterInitialization = false
        playbackJob?.cancel()
        playbackJob = null
        tts?.stop()
        currentPage = null
        lastSpokenSignature = null
        updateState(State.IDLE)
    }

    fun updateSettings(rate: Float, pitch: Float) {
        preferences.edit()
            .putFloat(KEY_RATE, rate.coerceIn(MIN_RATE, MAX_RATE))
            .putFloat(KEY_PITCH, pitch.coerceIn(MIN_PITCH, MAX_PITCH))
            .apply()
        applySpeechSettings()
        if (state == State.PLAYING) {
            tts?.stop()
            speakCurrentPage(reusePausedPage = true)
        }
    }

    fun destroy() {
        playbackJob?.cancel()
        playbackJob = null
        tts?.stop()
        tts?.shutdown()
        tts = null
        initializationComplete = false
    }

    private fun initialize() {
        if (tts != null) {
            updateState(State.INITIALIZING)
            return
        }
        updateState(State.INITIALIZING)
        tts = TextToSpeech(appContext) { status ->
            if (status != TextToSpeech.SUCCESS) {
                fail("Не удалось запустить синтез речи на устройстве")
                return@TextToSpeech
            }
            val engine = tts ?: return@TextToSpeech
            val russianLocale = Locale.Builder().setLanguage("ru").setRegion("RU").build()
            if (engine.setLanguage(russianLocale) < TextToSpeech.LANG_AVAILABLE) {
                fail("Русский язык не поддерживается установленным синтезатором речи")
                return@TextToSpeech
            }
            val offlineRussianVoice = engine.voices
                ?.asSequence()
                ?.filter { it.locale.language.equals("ru", ignoreCase = true) }
                ?.filterNot { it.isNetworkConnectionRequired }
                ?.sortedWith(compareByDescending<android.speech.tts.Voice> { it.quality }.thenBy { it.name })
                ?.firstOrNull()
            if (offlineRussianVoice == null) {
                fail("На телефоне не установлен русский офлайн-голос")
                return@TextToSpeech
            }
            if (engine.setVoice(offlineRussianVoice) == TextToSpeech.ERROR) {
                fail("Не удалось выбрать русский офлайн-голос")
                return@TextToSpeech
            }
            engine.setAudioAttributes(
                AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                    .build()
            )
            engine.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
                override fun onStart(utteranceId: String?) = Unit

                override fun onDone(utteranceId: String?) {
                    scope.launch { advanceAfterPage() }
                }

                @Deprecated("Deprecated in Java")
                override fun onError(utteranceId: String?) {
                    scope.launch { fail("Ошибка синтеза речи") }
                }

                override fun onError(utteranceId: String?, errorCode: Int) {
                    scope.launch { fail("Ошибка синтеза речи: $errorCode") }
                }
            })
            initializationComplete = true
            applySpeechSettings()
            if (resumeAfterInitialization) {
                resumeAfterInitialization = false
                updateState(State.PLAYING)
                speakCurrentPage(reusePausedPage = false)
            } else {
                updateState(State.IDLE)
            }
        }
    }

    private fun speakCurrentPage(reusePausedPage: Boolean) {
        playbackJob?.cancel()
        playbackJob = scope.launch {
            val page = if (reusePausedPage) currentPage else null
                ?: reader.getSpeechPage(TextToSpeech.getMaxSpeechInputLength().coerceAtMost(MAX_PAGE_CHARS))
            if (state != State.PLAYING) return@launch
            if (page == null || page.text.isBlank()) {
                fail("На текущей странице нет текста для озвучивания")
                return@launch
            }
            currentPage = page
            val signature = page.signature()
            if (!reusePausedPage && signature == lastSpokenSignature) {
                stop()
                onMessage("Достигнут конец книги")
                return@launch
            }
            lastSpokenSignature = signature
            applySpeechSettings()
            val result = tts?.speak(
                page.text,
                TextToSpeech.QUEUE_FLUSH,
                null,
                UUID.randomUUID().toString()
            ) ?: TextToSpeech.ERROR
            if (result == TextToSpeech.ERROR) fail("Не удалось начать озвучивание")
        }
    }

    private suspend fun advanceAfterPage() {
        if (state != State.PLAYING) return
        val page = currentPage ?: return
        if (!page.canAdvance) {
            stop()
            onMessage("Книга прочитана до конца")
            return
        }
        currentPage = null
        reader.next()
        delay(PAGE_TURN_DELAY_MS)
        if (state == State.PLAYING) speakCurrentPage(reusePausedPage = false)
    }

    private fun applySpeechSettings() {
        tts?.setSpeechRate(rate)
        tts?.setPitch(pitch)
    }

    private fun fail(message: String) {
        playbackJob?.cancel()
        playbackJob = null
        tts?.stop()
        updateState(State.ERROR)
        onMessage(message)
    }

    private fun updateState(next: State) {
        state = next
        onStateChanged(next)
    }

    private fun ReaderSpeechPage.signature(): String = "$locator|${text.take(160)}"

    companion object {
        const val MIN_RATE = 0.55f
        const val MAX_RATE = 1.80f
        const val MIN_PITCH = 0.75f
        const val MAX_PITCH = 1.30f
        private const val DEFAULT_RATE = 1.0f
        private const val DEFAULT_PITCH = 1.0f
        private const val PREFERENCES_NAME = "reader_speech"
        private const val KEY_RATE = "rate"
        private const val KEY_PITCH = "pitch"
        private const val MAX_PAGE_CHARS = 3_500
        private const val PAGE_TURN_DELAY_MS = 450L
    }
}
