package com.cyjai.agent

import android.content.Context
import android.util.Log
import kotlinx.coroutines.*
import java.io.*
import java.net.HttpURLConnection
import java.net.URL

/**
 * 语音模型下载管理器
 * 首次启动时自动下载 SenseVoice + Silero VAD 模型到内部存储
 *
 * 模型来源：HuggingFace (csukuangfj)
 * 总大小：约 230MB（SenseVoice int8 228MB + tokens 308KB + Silero VAD 1.5MB）
 */
object ModelManager {
    private const val TAG = "ModelManager"

    // 模型下载地址（从自有服务器下载，3 个文件约 230MB）
    // 需提前上传到服务器静态目录：/static/voice_models/
    private const val BASE_URL = "https://agent.cyjai.com/static/voice_models"
    private const val SENSEVOICE_MODEL_URL = "$BASE_URL/model.int8.onnx"
    private const val SENSEVOICE_TOKENS_URL = "$BASE_URL/tokens.txt"
    private const val SILERO_VAD_URL = "$BASE_URL/silero_vad.onnx"

    private var _modelDir: File? = null

    fun init(context: Context) {
        _modelDir = File(context.filesDir, "voice_models")
    }

    private fun getModelDir(context: Context): File {
        return _modelDir ?: File(context.filesDir, "voice_models").also { _modelDir = it }
    }

    // 模型文件预期大小（字节），用于校验下载完整性（±5% 容差）
    private const val EXPECTED_MODEL_SIZE = 239_233_841L  // model.int8.onnx ~228MB
    private const val EXPECTED_TOKENS_SIZE = 316_000L      // tokens.txt ~308KB
    private const val EXPECTED_VAD_SIZE = 644_000L         // silero_vad.onnx ~629KB
    private const val SIZE_TOLERANCE = 0.05                // 5% 容差

    private fun isSizeValid(file: File, expected: Long): Boolean {
        if (!file.exists()) return false
        val size = file.length()
        val lower = (expected * (1.0 - SIZE_TOLERANCE)).toLong()
        val upper = (expected * (1.0 + SIZE_TOLERANCE)).toLong()
        return size in lower..upper
    }

    /**
     * 检查模型是否已下载完毕且文件完整
     */
    fun isModelReady(context: Context): Boolean {
        val dir = getModelDir(context)
        val senseVoiceModel = File(dir, "sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17/model.int8.onnx")
        val senseVoiceTokens = File(dir, "sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17/tokens.txt")
        val sileroVad = File(dir, "silero_vad.onnx")
        return isSizeValid(senseVoiceModel, EXPECTED_MODEL_SIZE)
                && isSizeValid(senseVoiceTokens, EXPECTED_TOKENS_SIZE)
                && isSizeValid(sileroVad, EXPECTED_VAD_SIZE)
    }

    /**
     * 删除所有已下载的模型文件
     */
    fun deleteModels(context: Context): Boolean {
        val dir = getModelDir(context)
        return try {
            dir.deleteRecursively()
        } catch (e: Exception) {
            Log.e(TAG, "删除模型文件失败", e)
            false
        }
    }

    /**
     * 下载所有模型（应在后台线程调用）
     * @param onProgress 进度回调 (总百分比: Int, 阶段描述: String)
     * @return 是否全部成功
     */
    suspend fun downloadModels(
        context: Context,
        onProgress: (Int, String) -> Unit = { _, _ -> }
    ): Boolean = withContext(Dispatchers.IO) {
        val dir = getModelDir(context)
        val senseVoiceDir = File(dir, "sherpa-onnx-sense-voice-zh-en-ja-ko-yue-int8-2024-07-17")
        senseVoiceDir.mkdirs()

        try {
            // 1. Silero VAD (~1.5MB)
            val sileroFile = File(dir, "silero_vad.onnx")
            if (!sileroFile.exists()) {
                onProgress(0, "下载 VAD 模型...")
                downloadFile(SILERO_VAD_URL, sileroFile) { pct ->
                    onProgress(pct * 5 / 100, "下载 VAD 模型... ${pct}%")
                }
            }

            // 2. tokens.txt (~308KB)
            val tokensFile = File(senseVoiceDir, "tokens.txt")
            if (!tokensFile.exists()) {
                onProgress(5, "下载 tokens...")
                downloadFile(SENSEVOICE_TOKENS_URL, tokensFile) { pct ->
                    onProgress(5 + pct * 2 / 100, "下载 tokens... ${pct}%")
                }
            }

            // 3. SenseVoice int8 (~228MB)
            val modelFile = File(senseVoiceDir, "model.int8.onnx")
            if (!modelFile.exists()) {
                onProgress(7, "下载 SenseVoice 模型（约 228MB）...")
                downloadFile(SENSEVOICE_MODEL_URL, modelFile) { pct ->
                    onProgress(7 + pct * 93 / 100, "下载 SenseVoice 模型... ${pct}%")
                }
            }

            onProgress(100, "模型就绪")
            Log.i(TAG, "模型下载完成")
            true
        } catch (e: Exception) {
            Log.e(TAG, "模型下载失败", e)
            false
        }
    }

    private fun downloadFile(urlStr: String, dest: File, onProgress: (Int) -> Unit) {
        var attempt = 0
        val maxAttempts = 3

        while (attempt < maxAttempts) {
            try {
                attempt++
                doDownload(urlStr, dest, onProgress)
                return
            } catch (e: Exception) {
                Log.w(TAG, "下载失败 (第${attempt}次): ${dest.name}", e)
                if (attempt >= maxAttempts) throw e
                Thread.sleep(2000) // 重试前等待
            }
        }
    }

    private fun doDownload(urlStr: String, dest: File, onProgress: (Int) -> Unit) {
        val url = URL(urlStr)
        val conn = url.openConnection() as HttpURLConnection
        conn.requestMethod = "GET"
        conn.connectTimeout = 15000
        conn.readTimeout = 120000

        // 断点续传
        var downloaded = if (dest.exists()) dest.length() else 0L
        if (downloaded > 0) {
            conn.setRequestProperty("Range", "bytes=$downloaded-")
        }

        conn.connect()

        val totalSize = if (downloaded > 0) {
            conn.getHeaderField("Content-Range")?.substringAfter("/")?.toLongOrNull()
                ?: (conn.contentLengthLong + downloaded)
        } else {
            conn.contentLengthLong
        }

        val inputStream = conn.inputStream
        val outputStream = FileOutputStream(dest, downloaded > 0)
        val buffer = ByteArray(65536)
        var bytesRead: Int
        var totalRead = downloaded
        var lastProgressReport = System.currentTimeMillis()

        while (inputStream.read(buffer).also { bytesRead = it } != -1) {
            outputStream.write(buffer, 0, bytesRead)
            totalRead += bytesRead

            // 限流进度回调（每 200ms 最多报一次）
            val now = System.currentTimeMillis()
            if (totalSize > 0 && now - lastProgressReport > 200) {
                val pct = (totalRead * 100 / totalSize).toInt()
                onProgress(pct.coerceIn(0, 100))
                lastProgressReport = now
            }
        }

        if (totalSize > 0 && totalRead < totalSize) {
            throw IOException("下载不完整: 期望 ${totalSize} 字节，实际 ${totalRead} 字节")
        }

        inputStream.close()
        outputStream.close()
        conn.disconnect()
    }
}
