package top.yeij.cyrene.voice.stt

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.media.AudioAttributes
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.util.Log
import androidx.core.content.ContextCompat
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import okio.ByteString
import java.util.UUID
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger

class DashScopeSttService(
    private val context: Context,
) : SttProvider {

    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private val gson = Gson()
    private val connectionId = AtomicInteger(0)

    private var webSocket: WebSocket? = null
    private var audioRecord: AudioRecord? = null
    private var recordingJob: Job? = null
    private var taskId: String = ""

    private val _partialResult = MutableStateFlow("")
    override val partialResult: StateFlow<String> = _partialResult.asStateFlow()

    private val _finalResult = MutableSharedFlow<SttResult>(extraBufferCapacity = 16)
    override val finalResult: SharedFlow<SttResult> = _finalResult.asSharedFlow()

    private val _onError = MutableSharedFlow<String>(extraBufferCapacity = 8)
    override val onError: SharedFlow<String> = _onError.asSharedFlow()

    private val _isListening = MutableStateFlow(false)
    override val isListening: StateFlow<Boolean> = _isListening.asStateFlow()

    // Configurable via settings
    var apiKey: String = ""
    var endpoint: String = "wss://dashscope.aliyuncs.com/api-ws/v1/inference"
    var model: String = "fun-asr-realtime"
    var enableInterimResults: Boolean = true

    private val sampleRate = 16000
    private val audioFormat = AudioFormat.ENCODING_PCM_16BIT
    private val channelConfig = AudioFormat.CHANNEL_IN_MONO
    private val bytesPerSample = 2
    private val chunkMs = 100
    private val bufferSize: Int = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
        .coerceAtLeast(sampleRate * bytesPerSample * chunkMs / 1000)

    override fun start() {
        if (apiKey.isBlank()) {
            Log.w(TAG, "DashScope API key not configured")
            _onError.tryEmit("请先配置 DashScope API Key")
            return
        }

        if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO)
            != PackageManager.PERMISSION_GRANTED
        ) {
            _onError.tryEmit("缺少录音权限")
            return
        }

        cancel()
        taskId = UUID.randomUUID().toString()
        _isListening.value = true
        _partialResult.value = ""

        connectWebSocket()
        startRecording()
    }

    override fun stop() {
        if (!_isListening.value) return
        Log.d(TAG, "Stopping recognition, taskId=$taskId")
        stopRecording()
        sendFinishTask()
    }

    override fun cancel() {
        if (!_isListening.value) return
        Log.d(TAG, "Cancelling recognition, taskId=$taskId")
        stopRecording()
        closeWebSocket()
        _isListening.value = false
        _partialResult.value = ""
    }

    private fun connectWebSocket() {
        val client = OkHttpClient.Builder()
            .readTimeout(0, TimeUnit.MILLISECONDS)
            .writeTimeout(0, TimeUnit.MILLISECONDS)
            .callTimeout(0, TimeUnit.MILLISECONDS)
            .build()

        val request = Request.Builder()
            .url(endpoint)
            .header("Authorization", "Bearer $apiKey")
            .build()

        val connId = connectionId.incrementAndGet()
        Log.i(TAG, "[#$connId] Connecting to DashScope: $endpoint")

        webSocket = client.newWebSocket(request, object : WebSocketListener() {
            override fun onOpen(webSocket: WebSocket, response: Response) {
                if (connectionId.get() != connId) return
                Log.i(TAG, "[#$connId] Connected, sending run-task")
                sendRunTask()
            }

            override fun onMessage(webSocket: WebSocket, text: String) {
                if (connectionId.get() != connId) return
                handleServerMessage(text)
            }

            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                if (connectionId.get() != connId) return
                Log.e(TAG, "[#$connId] WebSocket failure: ${t.message}", t)
                _isListening.value = false
                _onError.tryEmit("语音识别连接失败: ${t.message}")
            }

            override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
                if (connectionId.get() != connId) return
                Log.d(TAG, "[#$connId] Server closing: $code $reason")
            }

            override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
                if (connectionId.get() != connId) return
                Log.d(TAG, "[#$connId] Closed: $code $reason")
                _isListening.value = false
            }
        })
    }

    private fun sendRunTask() {
        val msg = mapOf(
            "header" to mapOf(
                "action" to "run-task",
                "task_id" to taskId,
                "streaming" to "duplex",
            ),
            "payload" to mapOf(
                "model" to model,
                "parameters" to mapOf(
                    "format" to "pcm",
                    "sample_rate" to sampleRate,
                ),
                "input" to emptyMap<String, String>(),
            ),
        )
        val json = gson.toJson(msg)
        Log.d(TAG, "Sending run-task: $json")
        webSocket?.send(json)
    }

    private fun sendFinishTask() {
        val msg = mapOf(
            "header" to mapOf(
                "action" to "finish-task",
                "task_id" to taskId,
            ),
            "payload" to emptyMap<String, String>(),
        )
        val json = gson.toJson(msg)
        Log.d(TAG, "Sending finish-task: $json")
        webSocket?.send(json)
    }

    private fun handleServerMessage(text: String) {
        try {
            val response = gson.fromJson(text, DashScopeResponse::class.java)
            val header = response.header ?: return
            val event = header.event ?: return

            when (event) {
                "result-generated" -> {
                    val sentence = response.payload?.output?.sentence ?: return
                    val sentenceText = sentence.text ?: return
                    _partialResult.value = sentenceText

                    val sttResult = SttResult(
                        text = sentenceText,
                        isFinal = sentence.sentenceEnd ?: true,
                    )
                    if (sttResult.isFinal) {
                        _finalResult.tryEmit(sttResult)
                    } else if (enableInterimResults) {
                        _finalResult.tryEmit(sttResult)
                    }
                }

                "task-finished" -> {
                    Log.i(TAG, "Task finished: $taskId")
                    // If no result-generated was received, emit what we have
                    val partial = _partialResult.value
                    if (partial.isNotBlank()) {
                        _finalResult.tryEmit(SttResult(text = partial, isFinal = true))
                    }
                    closeWebSocket()
                    _isListening.value = false
                }

                "task-failed" -> {
                    val error = header.errorMessage ?: "语音识别失败"
                    Log.e(TAG, "Task failed: $error")
                    _onError.tryEmit(error)
                    closeWebSocket()
                    _isListening.value = false
                }
            }
        } catch (e: Exception) {
            Log.w(TAG, "Failed to parse server message: ${e.message}")
        }
    }

    private fun startRecording() {
        try {
            audioRecord = AudioRecord(
                MediaRecorder.AudioSource.VOICE_RECOGNITION,
                sampleRate,
                channelConfig,
                audioFormat,
                bufferSize,
            ).also {
                if (it.state != AudioRecord.STATE_INITIALIZED) {
                    Log.e(TAG, "AudioRecord initialization failed")
                    _onError.tryEmit("麦克风初始化失败")
                    _isListening.value = false
                    return
                }
                it.startRecording()
            }

            Log.d(TAG, "Recording started, bufferSize=$bufferSize")
            val readBuffer = ByteArray(bufferSize)

            recordingJob = scope.launch {
                while (isActive && _isListening.value) {
                    val bytesRead = audioRecord?.read(readBuffer, 0, readBuffer.size) ?: -1
                    if (bytesRead > 0) {
                        val data = readBuffer.copyOf(bytesRead)
                        webSocket?.let { ws ->
                            try {
                                ws.send(ByteString.of(*data))
                            } catch (e: Exception) {
                                Log.w(TAG, "Failed to send audio: ${e.message}")
                            }
                        }
                    } else if (bytesRead < 0) {
                        Log.w(TAG, "AudioRecord read error: $bytesRead")
                        break
                    }
                }
            }
        } catch (e: SecurityException) {
            Log.e(TAG, "Missing RECORD_AUDIO permission", e)
            _onError.tryEmit("缺少录音权限")
            _isListening.value = false
        } catch (e: Exception) {
            Log.e(TAG, "Failed to start recording", e)
            _onError.tryEmit("录音启动失败: ${e.message}")
            _isListening.value = false
        }
    }

    private fun stopRecording() {
        recordingJob?.cancel()
        recordingJob = null
        try {
            audioRecord?.stop()
            audioRecord?.release()
        } catch (e: Exception) {
            Log.w(TAG, "Error releasing AudioRecord: ${e.message}")
        }
        audioRecord = null
        Log.d(TAG, "Recording stopped")
    }

    private fun closeWebSocket() {
        try {
            webSocket?.close(1000, "Done")
        } catch (_: Exception) { }
        webSocket = null
    }

    fun shutdown() {
        cancel()
        scope.cancel()
    }

    companion object {
        private const val TAG = "DashScopeSTT"
    }
}

// --- JSON DTOs for DashScope WebSocket protocol ---

data class DashScopeResponse(
    @SerializedName("header") val header: DashScopeHeader?,
    @SerializedName("payload") val payload: DashScopePayload?,
)

data class DashScopeHeader(
    @SerializedName("task_id") val taskId: String?,
    @SerializedName("event") val event: String?,
    @SerializedName("action") val action: String?,
    @SerializedName("error_message") val errorMessage: String?,
)

data class DashScopePayload(
    @SerializedName("output") val output: DashScopeOutput?,
    @SerializedName("usage") val usage: Map<String, Any>?,
)

data class DashScopeOutput(
    @SerializedName("sentence") val sentence: DashScopeSentence?,
)

data class DashScopeSentence(
    @SerializedName("text") val text: String?,
    @SerializedName("sentence_end") val sentenceEnd: Boolean?,
    @SerializedName("sentence_id") val sentenceId: Int?,
    @SerializedName("begin_time") val beginTime: Long?,
    @SerializedName("end_time") val endTime: Long?,
    @SerializedName("words") val words: List<Map<String, Any>>?,
    @SerializedName("emo_tag") val emoTag: String?,
)
