package ai.guiji.duix.test.ui.activity

import ai.guiji.duix.sdk.client.Constant
import ai.guiji.duix.sdk.client.DUIX
import ai.guiji.duix.sdk.client.loader.ModelInfo
import ai.guiji.duix.sdk.client.render.DUIXRenderer
import ai.guiji.duix.sdk.client.thread.RenderThread
import ai.guiji.duix.test.R
import ai.guiji.duix.test.audio.AudioResampler
import ai.guiji.duix.test.audio.StreamingAudioRecorder
import ai.guiji.duix.test.camera.CameraFrameCapture
import ai.guiji.duix.test.databinding.ActivityCallBinding
import ai.guiji.duix.test.realtime.OmniRealtimeClient
import ai.guiji.duix.test.ui.adapter.MotionAdapter
import ai.guiji.duix.test.ui.dialog.AudioRecordDialog
import ai.guiji.duix.test.ui.widget.AudioSpectrumView
import ai.guiji.duix.test.util.StringUtils
import android.Manifest
import android.annotation.SuppressLint
import android.opengl.GLSurfaceView
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.CompoundButton
import android.widget.Toast
import com.bumptech.glide.Glide
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream


class CallActivity : BaseActivity() {

    companion object {
        const val GL_CONTEXT_VERSION = 2
    }

    private var modelUrl = ""
    private var voice = "Ethan"
    private var debug = false
    private var mMessage = ""

    @SuppressLint("SetTextI18n")
    private fun applyMessage(msg: String){
        if (debug){
            runOnUiThread {
                binding.tvDebug.visibility = View.VISIBLE
                if (mMessage.length > 10000){
                    mMessage = ""
                }
                mMessage = "${StringUtils.dateToStringMS4()} $msg\n$mMessage"
                binding.tvDebug.text = mMessage
            }
        }

    }

    private lateinit var binding: ActivityCallBinding
    private var duix: DUIX? = null
    private var mDUIXRender: DUIXRenderer? = null
    private var mModelInfo: ModelInfo?=null     // 加载的模型信息

    // AI 实时对话
    private var omniClient: OmniRealtimeClient? = null
    private var streamingRecorder: StreamingAudioRecorder? = null
    private var cameraFrameCapture: CameraFrameCapture? = null
    private var isAIConversationActive = false
    private var isCameraEnabled = false
    private val mainHandler = Handler(Looper.getMainLooper())
    private var assistantTextBuffer = StringBuilder()
    private var hasStartedPushForResponse = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        keepScreenOn()
//        val audioManager = mContext.getSystemService(AUDIO_SERVICE) as AudioManager
//        audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
//        audioManager.isSpeakerphoneOn = true
        binding = ActivityCallBinding.inflate(layoutInflater)
        setContentView(binding.root)

        modelUrl = intent.getStringExtra("modelUrl") ?: ""
        voice = intent.getStringExtra("voice") ?: "Ethan"
        debug = intent.getBooleanExtra("debug", false)

        Glide.with(mContext).load("file:///android_asset/bg/bg1.png").into(binding.ivBg)

        binding.glTextureView.setEGLContextClientVersion(GL_CONTEXT_VERSION)
        binding.glTextureView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
//        binding.glTextureView.preserveEGLContextOnPause = true
        binding.glTextureView.isOpaque = false

        binding.switchMute.setOnCheckedChangeListener(object : CompoundButton.OnCheckedChangeListener {
            override fun onCheckedChanged(
                buttonView: CompoundButton?,
                isChecked: Boolean,
            ) {
                if (isChecked) {
                    duix?.setVolume(0.0F)
                } else {
                    duix?.setVolume(1.0F)
                }
            }
        })

        binding.btnRecord.setOnClickListener {
            requestPermission(arrayOf(Manifest.permission.RECORD_AUDIO), 1)
        }

        binding.btnPlayPCM.setOnClickListener {
            applyMessage("start play pcm")
            playPCMStream()
        }

        binding.btnPlayWAV.setOnClickListener {
            applyMessage("start play wav")
            playWAVFile()
        }

        binding.btnRandomMotion.setOnClickListener {
            applyMessage("start random motion")
            duix?.startRandomMotion(true)
        }
        binding.btnAIConversation.setOnClickListener {
            requestPermission(arrayOf(Manifest.permission.RECORD_AUDIO), 2)
        }

        binding.btnCameraToggle.setOnClickListener {
            if (isCameraEnabled) {
                toggleCamera()
            } else {
                requestPermission(arrayOf(Manifest.permission.CAMERA), 3)
            }
        }

        mDUIXRender =
            DUIXRenderer(
                mContext,
                binding.glTextureView
            )
        binding.glTextureView.setRenderer(mDUIXRender)
        binding.glTextureView.renderMode =
            GLSurfaceView.RENDERMODE_WHEN_DIRTY      // 一定要在设置完Render之后再调用

        duix = DUIX(mContext, modelUrl, mDUIXRender) { event, msg, info ->
            when (event) {
                Constant.CALLBACK_EVENT_INIT_READY -> {
                    mModelInfo = info as ModelInfo
                    Log.i(TAG, "CALLBACK_EVENT_INIT_READY: $mModelInfo")
                    initOk()
                }

                Constant.CALLBACK_EVENT_INIT_ERROR -> {
                    runOnUiThread {
                        applyMessage("init error: $msg")
                        Log.e(TAG, "CALLBACK_EVENT_INIT_ERROR: $msg")
                        Toast.makeText(mContext, "Initialization exception: $msg", Toast.LENGTH_SHORT).show()
                    }
                }

                Constant.CALLBACK_EVENT_AUDIO_PLAY_START -> {
                    applyMessage("callback audio play start")
                    Log.i(TAG, "CALLBACK_EVENT_AUDIO_PLAY_START")
                    if (isAIConversationActive) {
                        runOnUiThread { binding.audioSpectrum.setAiSpeaking(true) }
                    }
                }

                Constant.CALLBACK_EVENT_AUDIO_PLAY_END -> {
                    applyMessage("callback audio play end")
                    Log.i(TAG, "CALLBACK_EVENT_PLAY_END")
                    if (isAIConversationActive) {
                        runOnUiThread { binding.audioSpectrum.setAiSpeaking(false) }
                    }
                }

                Constant.CALLBACK_EVENT_AUDIO_PLAY_ERROR -> {
                    applyMessage("callback audio play error: $msg")
                    Log.e(TAG, "CALLBACK_EVENT_PLAY_ERROR: $msg")
                }

                Constant.CALLBACK_EVENT_MOTION_START -> {
                    applyMessage("callback motion play start")
                    Log.e(TAG, "CALLBACK_EVENT_MOTION_START")
                }

                Constant.CALLBACK_EVENT_MOTION_END -> {
                    applyMessage("callback motion play end")
                    Log.e(TAG, "CALLBACK_EVENT_MOTION_END")
                }
            }
        }
        // Rendering status callback
//        duix?.setReporter(object : RenderThread.Reporter {
//            override fun onRenderStat(
//                resultCode: Int,
//                isLip: Boolean,
//                useTime: Long,
//            ) {
//
//            }
//        })
        applyMessage("start init")
        duix?.init()
    }

    private fun initOk() {
        Log.i(TAG, "init ok")
        applyMessage("init ok")
        runOnUiThread {
            binding.btnRecord.isEnabled = true
            binding.btnPlayPCM.isEnabled = true
            binding.btnPlayWAV.isEnabled = true
            binding.switchMute.isEnabled = true
            binding.btnAIConversation.isEnabled = true

            if (debug) {
                binding.btnPlayPCM.visibility = View.VISIBLE
                binding.btnPlayWAV.visibility = View.VISIBLE
            }

            mModelInfo?.let { modelInfo ->
                if (modelInfo.motionRegions.isNotEmpty()) {
                    val names = ArrayList<String>()
                    for (motion in modelInfo.motionRegions){
                        if (!TextUtils.isEmpty(motion.name) && "unknown" != motion.name){
                            names.add(motion.name)
                        }
                    }
                    // Named action regions
                    if (names.isNotEmpty()){
                        val motionAdapter = MotionAdapter(names, object : MotionAdapter.Callback{
                            override fun onClick(name: String, now: Boolean) {
                                applyMessage("start [${name}] motion")
                                duix?.startMotion(name, now)
                            }
                        })
                        binding.rvMotion.adapter = motionAdapter
                    }
                    binding.btnRandomMotion.visibility = View.VISIBLE
                    binding.tvMotionTips.visibility = View.VISIBLE
                }
            }
        }
    }


    override fun onDestroy() {
        super.onDestroy()
        stopAIConversation()
        duix?.release()
    }

    private fun playPCMStream(){
        val thread = Thread {
            duix?.startPush()
            val inputStream = assets.open("pcm/2.pcm")
            val buffer = ByteArray(320)
            var length: Int
            while (inputStream.read(buffer).also { length = it } > 0){
                val data = buffer.copyOfRange(0, length)
                duix?.pushPcm(data)
            }
            duix?.stopPush()
            inputStream.close()
        }
        thread.start()
    }

    private fun playWAVFile(){
        val thread = Thread {
            val wavName = "1.wav"
            val wavFile = File(mContext.externalCacheDir, wavName)
            if (!wavFile.exists()){
                // copy assets -> sd card
                val inputStream = assets.open("wav/$wavName")
                if (!mContext.externalCacheDir!!.exists()){
                    mContext.externalCacheDir!!.mkdirs()
                }
                val out = FileOutputStream(wavFile)
                val buffer = ByteArray(1024)
                var length: Int
                while ((inputStream.read(buffer).also { length = it }) > 0) {
                    out.write(buffer, 0, length)
                }
                out.close()
                inputStream.close()
            }
            duix?.playAudio(wavFile.absolutePath)
        }
        thread.start()
    }

    override fun permissionsGet(get: Boolean, code: Int) {
        super.permissionsGet(get, code)
        when (code) {
            1 -> if (get) showRecordDialog() else Toast.makeText(mContext, R.string.need_permission_continue, Toast.LENGTH_SHORT).show()
            2 -> if (get) toggleAIConversation() else Toast.makeText(mContext, R.string.need_permission_continue, Toast.LENGTH_SHORT).show()
            3 -> if (get) toggleCamera() else Toast.makeText(mContext, R.string.need_permission_continue, Toast.LENGTH_SHORT).show()
        }
    }

    private fun toggleAIConversation() {
        if (isAIConversationActive) {
            stopAIConversation()
        } else {
            startAIConversation()
        }
    }

    private fun startAIConversation() {
        if (duix?.isReady() != true) {
            Toast.makeText(mContext, "数字人未就绪", Toast.LENGTH_SHORT).show()
            return
        }
        applyMessage("AI对话: 连接中...")
        binding.tvSubtitle.visibility = View.VISIBLE
        binding.tvSubtitle.text = "正在连接..."
        binding.audioSpectrum.visibility = View.VISIBLE
        binding.audioSpectrum.clear()
        binding.btnAIConversation.setImageResource(R.drawable.ic_mic_off)
        binding.btnAIConversation.setBackgroundResource(R.drawable.shape_fab_ai_active)

        omniClient = OmniRealtimeClient(voice, object : OmniRealtimeClient.Callback {
            override fun onConnected() {
                mainHandler.post {
                    binding.tvSubtitle.text = "已连接，请对着麦克风说话"
                    binding.btnCameraToggle.visibility = View.VISIBLE
                    binding.btnCameraToggle.setBackgroundResource(R.drawable.shape_fab_camera_off)
                    binding.btnCameraToggle.contentDescription = getString(R.string.camera_off)
                    isCameraEnabled = false
                    applyMessage("AI对话: 已连接")
                    streamingRecorder = StreamingAudioRecorder(mContext, object : StreamingAudioRecorder.StreamingCallback {
                        override fun onAudioData(pcmData: ByteArray) {
                            omniClient?.appendAudio(pcmData)
                            mainHandler.post { binding.audioSpectrum.updateAudio(pcmData) }
                        }
                        override fun onError(message: String) {
                            mainHandler.post {
                                Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
                            }
                        }
                    })
                    streamingRecorder?.start()
                }
            }

            override fun onDisconnected() {
                mainHandler.post { stopAIConversation() }
            }

            override fun onError(message: String) {
                mainHandler.post {
                    Toast.makeText(mContext, "AI错误: $message", Toast.LENGTH_SHORT).show()
                    binding.tvSubtitle.text = "错误: $message"
                }
            }

            override fun onUserSpeechStarted() {
                mainHandler.post {
                    binding.tvSubtitle.text = "正在聆听..."
                }
            }

            override fun onUserSpeechStopped() {
                mainHandler.post {
                    binding.tvSubtitle.text = "思考中..."
                }
            }

            override fun onUserTranscript(transcript: String) {
                mainHandler.post {
                    binding.tvSubtitle.text = "您: $transcript"
                }
            }

            override fun onAssistantTextDelta(delta: String) {
                mainHandler.post {
                    assistantTextBuffer.append(delta)
                    binding.tvSubtitle.text = assistantTextBuffer.toString()
                }
            }

            override fun onAssistantTranscriptDone(transcript: String) {
                mainHandler.post {
                    assistantTextBuffer.clear()
                    assistantTextBuffer.append(transcript)
                    binding.tvSubtitle.text = transcript
                }
            }

            override fun onAssistantAudioDelta(pcmData: ByteArray) {
                if (!hasStartedPushForResponse) {
                    hasStartedPushForResponse = true
                    duix?.startPush()
                }
                val resampled = AudioResampler.resample24kTo16k(pcmData)
                if (resampled.isNotEmpty()) {
                    duix?.pushPcm(resampled)
                }
            }

            override fun onAssistantAudioDone() {
                if (hasStartedPushForResponse) {
                    duix?.stopPush()
                    hasStartedPushForResponse = false
                }
            }

            override fun onResponseDone() {
                mainHandler.post {
                    if (hasStartedPushForResponse) {
                        duix?.stopPush()
                        hasStartedPushForResponse = false
                    }
                    assistantTextBuffer.clear()
                }
            }
        })

        isAIConversationActive = true
        omniClient?.connect()
    }

    private fun stopAIConversation() {
        isAIConversationActive = false
        stopCamera()
        streamingRecorder?.release()
        streamingRecorder = null
        omniClient?.disconnect()
        omniClient = null
        duix?.stopAudio()
        assistantTextBuffer.clear()

        mainHandler.post {
            if (!isFinishing) {
                binding.btnAIConversation.setImageResource(R.drawable.ic_mic)
                binding.btnAIConversation.setBackgroundResource(R.drawable.shape_fab_ai)
                binding.btnCameraToggle.visibility = View.GONE
                binding.tvSubtitle.visibility = View.GONE
                binding.tvSubtitle.text = ""
                binding.audioSpectrum.clear()
                binding.audioSpectrum.visibility = View.GONE
                applyMessage("AI对话: 已结束")
            }
        }
    }

    private fun toggleCamera() {
        if (!isAIConversationActive || omniClient == null) return
        isCameraEnabled = !isCameraEnabled
        if (isCameraEnabled) {
            binding.cameraPreview.visibility = View.VISIBLE
            cameraFrameCapture = CameraFrameCapture(mContext, object : CameraFrameCapture.FrameCallback {
                override fun onFrame(jpegData: ByteArray) {
                    omniClient?.appendImage(jpegData)
                }
                override fun onError(message: String) {
                    mainHandler.post {
                        Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
                        stopCamera()
                    }
                }
            }, binding.cameraPreview)
            cameraFrameCapture?.start()
            binding.btnCameraToggle.setBackgroundResource(R.drawable.shape_fab_camera_on)
            binding.btnCameraToggle.contentDescription = getString(R.string.camera_on)
        } else {
            stopCamera()
            binding.btnCameraToggle.setBackgroundResource(R.drawable.shape_fab_camera_off)
            binding.btnCameraToggle.contentDescription = getString(R.string.camera_off)
        }
    }

    private fun stopCamera() {
        cameraFrameCapture?.stop()
        cameraFrameCapture = null
        isCameraEnabled = false
        binding.cameraPreview.visibility = View.GONE
    }

    private fun showRecordDialog(){
        val audioRecordDialog = AudioRecordDialog(mContext, object : AudioRecordDialog.Listener{
            override fun onFinish(path: String) {
                val thread = Thread {
                    duix?.startPush()
                    val inputStream = FileInputStream(path)
                    val buffer = ByteArray(320)
                    var length: Int
                    while (inputStream.read(buffer).also { length = it } > 0){
                        val data = buffer.copyOfRange(0, length)
                        duix?.pushPcm(data)
                    }
                    duix?.stopPush()
                    inputStream.close()
                }
                thread.start()
            }
        })
        audioRecordDialog.show()
    }
}
