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

import android.content.Context
import android.util.Log
import com.aletheia.app.ui.reader.ReaderSpeechPage
import com.aletheia.app.ui.reader.ReaderWebController
import java.util.concurrent.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

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 val engine = VoskSpeechEngine(appContext)
    private var state = State.IDLE
    private var initializationComplete = false
    private var resumeAfterInitialization = false
    private var initializationJob: Job? = null
    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)

    val pauseScale: Float
        get() = preferences.getFloat(KEY_PAUSE_SCALE, DEFAULT_PAUSE_SCALE).coerceIn(MIN_PAUSE_SCALE, MAX_PAUSE_SCALE)

    val articulationScale: Float
        get() = preferences.getFloat(KEY_ARTICULATION_SCALE, DEFAULT_ARTICULATION_SCALE)
            .coerceIn(MIN_ARTICULATION_SCALE, MAX_ARTICULATION_SCALE)

    val speakerId: Int
        get() = preferences.getInt(KEY_SPEAKER_ID, DEFAULT_SPEAKER_ID).coerceIn(0, VOICES.lastIndex)

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

    fun start() {
        if (!initializationComplete) {
            resumeAfterInitialization = true
            initialize()
            return
        }
        currentPage = null
        lastSpokenSignature = null
        updateState(State.PLAYING)
        speakCurrentPage(reusePausedPage = false)
    }

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

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

    fun updateSettings(
        rate: Float,
        pitch: Float,
        pauseScale: Float,
        articulationScale: Float,
        speakerId: Int
    ) {
        preferences.edit()
            .putFloat(KEY_RATE, rate.coerceIn(MIN_RATE, MAX_RATE))
            .putFloat(KEY_PITCH, pitch.coerceIn(MIN_PITCH, MAX_PITCH))
            .putFloat(KEY_PAUSE_SCALE, pauseScale.coerceIn(MIN_PAUSE_SCALE, MAX_PAUSE_SCALE))
            .putFloat(
                KEY_ARTICULATION_SCALE,
                articulationScale.coerceIn(MIN_ARTICULATION_SCALE, MAX_ARTICULATION_SCALE)
            )
            .putInt(KEY_SPEAKER_ID, speakerId.coerceIn(0, VOICES.lastIndex))
            .apply()
        if (state == State.PLAYING) {
            engine.stop()
            speakCurrentPage(reusePausedPage = true)
        }
    }

    fun destroy() {
        initializationJob?.cancel()
        playbackJob?.cancel()
        initializationJob = null
        playbackJob = null
        engine.destroy()
        initializationComplete = false
    }

    private fun initialize() {
        if (initializationJob?.isActive == true) return
        updateState(State.INITIALIZING)
        initializationJob = scope.launch {
            try {
                engine.initialize()
                initializationComplete = true
                if (resumeAfterInitialization) {
                    resumeAfterInitialization = false
                    updateState(State.PLAYING)
                    speakCurrentPage(reusePausedPage = false)
                } else {
                    updateState(State.IDLE)
                }
            } catch (error: Throwable) {
                Log.e(TAG, "Offline voice initialization failed", error)
                fail("Не удалось запустить встроенный офлайн-голос Vosk: ${error.message ?: error.javaClass.simpleName}")
            }
        }
    }

    private fun speakCurrentPage(reusePausedPage: Boolean) {
        playbackJob?.cancel()
        playbackJob = scope.launch {
            try {
                val page = if (reusePausedPage) currentPage else null
                    ?: reader.getSpeechPage(MAX_PAGE_CHARS)
                if (state != State.PLAYING) return@launch
                if (page == null || page.text.isBlank()) {
                    fail("На текущей странице нет текста для озвучивания")
                    return@launch
                }
                Log.d(
                    TAG,
                    "Speech page extracted: chars=${page.text.length}, pageChars=${page.pageCharacters}, " +
                        "extended=${page.extendedCharacters}, canAdvance=${page.canAdvance}, source=${page.source}"
                )
                currentPage = page
                val signature = page.signature()
                if (!reusePausedPage && signature == lastSpokenSignature) {
                    stop()
                    onMessage("Достигнут конец книги")
                    return@launch
                }
                lastSpokenSignature = signature
                engine.speak(page.text, rate, pitch, pauseScale, articulationScale)
                advanceAfterPage()
            } catch (_: CancellationException) {
                // Pause and stop cancel the active synthesis/playback job.
            } catch (error: Throwable) {
                if (state == State.PLAYING) {
                    Log.e(TAG, "Offline speech playback failed", error)
                    fail("Ошибка встроенного синтеза речи: ${error.message ?: error.javaClass.simpleName}")
                }
            }
        }
    }

    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 fail(message: String) {
        playbackJob?.cancel()
        playbackJob = null
        engine.stop()
        updateState(State.ERROR)
        onMessage(message)
    }

    private fun updateState(next: State) {
        Log.d(TAG, "State: $state -> $next")
        state = next
        onStateChanged(next)
    }

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

    companion object {
        private const val TAG = "AletheiaVoskTts"
        const val MIN_RATE = 0.55f
        const val MAX_RATE = 1.80f
        const val MIN_PITCH = 0.75f
        const val MAX_PITCH = 1.30f
        const val MIN_PAUSE_SCALE = 0.50f
        const val MAX_PAUSE_SCALE = 2.00f
        const val MIN_ARTICULATION_SCALE = 0.90f
        const val MAX_ARTICULATION_SCALE = 1.20f
        val VOICES = listOf("Мужской 1")
        private const val DEFAULT_RATE = 1.0f
        private const val DEFAULT_PITCH = 1.0f
        private const val DEFAULT_PAUSE_SCALE = 1.0f
        private const val DEFAULT_ARTICULATION_SCALE = 1.06f
        private const val DEFAULT_SPEAKER_ID = 2
        private const val PREFERENCES_NAME = "reader_speech"
        private const val KEY_RATE = "rate"
        private const val KEY_PITCH = "pitch"
        private const val KEY_PAUSE_SCALE = "pause_scale"
        private const val KEY_ARTICULATION_SCALE = "articulation_scale"
        private const val KEY_SPEAKER_ID = "speaker_id"
        private const val MAX_PAGE_CHARS = 3_500
        private const val PAGE_TURN_DELAY_MS = 450L
    }
}
