package com.tsmobile.app.voice

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.media.*
import android.media.audiofx.NoiseSuppressor
import android.os.Build
import android.util.Log
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlin.math.sqrt

/**
 * Captures outbound Opus and plays the single mixed PCM stream produced by Go.
 * Incoming audio is 48 kHz, interleaved stereo, signed PCM16 little-endian:
 * 960 samples per channel (3,840 bytes) per 20 ms frame.
 */
class VoiceService(
    private val context: Context,
    private val scope: CoroutineScope,
) {
    companion object {
        private const val TAG = "VoiceService"
        const val SAMPLE_RATE = MixedPcmFrame.SAMPLE_RATE
        const val CHANNELS = MixedPcmFrame.CHANNELS
        const val FRAME_DURATION_MS = 20
        // Outbound microphone capture remains mono: 960 samples per 20 ms frame.
        const val FRAME_SIZE = MixedPcmFrame.SAMPLES_PER_CHANNEL
        const val PCM_FRAME_BYTES = MixedPcmFrame.BYTES_PER_FRAME
        private const val PLAYBACK_QUEUE_CAPACITY = 12
        const val SPEAKING_THRESHOLD = 0.015f
        const val SPEAKING_TIMEOUT_MS = 800L
    }

    data class ReceiveDiagnostics(
        val acceptedFrames: Long,
        val droppedInvalidFrames: Long,
        val droppedQueueOverflow: Long,
        val trackWriteFailures: Long,
    )

    private var isCapturing = false
    private var isMuted = true
    @Volatile private var speakerEnabled = true
    private var denoiseEnabled = true
    private var audioRecord: AudioRecord? = null
    private var captureJob: Job? = null
    private var opusEncoder: OpusEncoder? = null
    private var noiseSuppressor: NoiseSuppressor? = null

    private val playbackLock = Any()
    private var playbackQueue: Channel<ByteArray>? = null
    private var playbackJob: Job? = null
    private var playbackGeneration = 0L
    private var activePlaybackTrack: AudioTrack? = null
    @Volatile private var receiveVolume = 1.0f
    @Volatile private var receiveGain = 5.0f

    private val audioManager = context.getSystemService(AudioManager::class.java)
    private var audioFocusRequest: AudioFocusRequest? = null
    private var ownsCommunicationMode = false
    private var hasAudioFocus = false

    @Volatile private var acceptedFrames = 0L
    @Volatile private var droppedInvalidFrames = 0L
    @Volatile private var droppedQueueOverflow = 0L
    @Volatile private var trackWriteFailures = 0L

    private var selfSpeakingJob: Job? = null
    var onVoiceData: ((ByteArray, Int) -> String)? = null
    var onVoiceSendError: ((String) -> Unit)? = null
    var onSelfSpeaking: ((Boolean) -> Unit)? = null
    private val recordBuffer = ShortArray(FRAME_SIZE)

    fun receiveDiagnostics() = ReceiveDiagnostics(
        acceptedFrames, droppedInvalidFrames, droppedQueueOverflow, trackWriteFailures,
    )

    fun isVoiceAvailable(): Boolean = OpusEncoder.isAvailable() &&
        context.checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED

    fun startCapture(): Boolean {
        if (isCapturing) return true
        if (!isVoiceAvailable()) return false
        return try {
            opusEncoder = OpusEncoder()
            if (!opusEncoder!!.start()) { opusEncoder = null; return false }
            val bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
                AudioFormat.ENCODING_PCM_16BIT).coerceAtLeast(FRAME_SIZE * 2)
            audioRecord = AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, SAMPLE_RATE,
                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize).apply {
                if (state != AudioRecord.STATE_INITIALIZED) { release(); audioRecord = null; return false }
            }
            setupNoiseSuppressor()
            audioRecord!!.startRecording()
            isCapturing = true
            captureJob = scope.launch(Dispatchers.IO) { captureLoop() }
            true
        } catch (e: Exception) {
            Log.e(TAG, "Failed to start capture", e)
            stopCapture()
            false
        }
    }

    fun stopCapture() {
        isCapturing = false
        captureJob?.cancel(); captureJob = null
        noiseSuppressor?.release(); noiseSuppressor = null
        try { audioRecord?.stop() } catch (_: Exception) {}
        audioRecord?.release(); audioRecord = null
        opusEncoder?.release(); opusEncoder = null
        setSelfSpeaking(false)
    }

    fun setMuted(muted: Boolean) { isMuted = muted }

    fun setSpeakerEnabled(enabled: Boolean) {
        speakerEnabled = enabled
        if (enabled) forceLoudspeaker()
        else stopReceivePlayback("speaker disabled")
    }

    fun setReceiveVolume(volume: Float) {
        val clamped = volume.coerceIn(0f, 1f)
        receiveVolume = clamped
        synchronized(playbackLock) {
            activePlaybackTrack?.setVolume(clamped)
        }
    }

    /** 设置 PCM 预增益（1.0 = 原始音量，2.5 = 默认） */
    fun setReceiveGain(gain: Float) {
        receiveGain = gain.coerceIn(0.5f, 5.0f)
    }

    fun enableReceivePlayback() {
        if (speakerEnabled) forceLoudspeaker()
    }

    fun setDenoiseEnabled(enabled: Boolean) {
        denoiseEnabled = enabled
        noiseSuppressor?.let { if (it.enabled != enabled) it.enabled = enabled }
    }

    /** Fast bridge handoff. The playback worker owns AudioTrack and discards oldest queued PCM on overflow. */
    fun handleMixedVoicePCM(pcm: ByteArray) {
        if (!speakerEnabled) return
        if (!MixedPcmFrame.isValid(pcm)) {
            droppedInvalidFrames++
            return
        }
        val queue = ensurePlaybackWorker()
        val copy = pcm.copyOf()
        if (!queue.trySend(copy).isSuccess) {
            queue.tryReceive().getOrNull()
            droppedQueueOverflow++
            if (!queue.trySend(copy).isSuccess) return
        }
        acceptedFrames++
        if (acceptedFrames <= 5 || acceptedFrames % 250L == 0L) {
            Log.i(TAG, "Mixed PCM accepted count=$acceptedFrames bytes=${pcm.size}")
        }
    }

    private fun ensurePlaybackWorker(): Channel<ByteArray> = synchronized(playbackLock) {
        playbackQueue ?: Channel<ByteArray>(PLAYBACK_QUEUE_CAPACITY).also { queue ->
            val generation = ++playbackGeneration
            playbackQueue = queue
            playbackJob = scope.launch(Dispatchers.IO) { playbackLoop(queue, generation) }
        }
    }

    private suspend fun playbackLoop(queue: Channel<ByteArray>, generation: Long) {
        var track: AudioTrack? = null
        try {
            for (frame in queue) {
                if (!speakerEnabled) continue
                if (track == null) {
                    track = createAudioTrack()
                    synchronized(playbackLock) { activePlaybackTrack = track }
                }
                val activeTrack = track ?: continue
                // 应用 PCM 预增益
                val gainedFrame = applyPcmGain(frame, receiveGain)
                if (acceptedFrames <= 5 || acceptedFrames % 250L == 0L) {
                    Log.i(TAG, "Writing mixed PCM bytes=${gainedFrame.size} gain=$receiveGain trackState=${activeTrack.state}")
                }
                val written = activeTrack.write(gainedFrame, 0, gainedFrame.size, AudioTrack.WRITE_BLOCKING)
                if (written != frame.size) {
                    trackWriteFailures++
                    Log.w(TAG, "AudioTrack.write incomplete: $written/${frame.size}")
                    releaseTrack(activeTrack)
                    track = null
                }
            }
        } catch (_: CancellationException) {
        } catch (e: Exception) {
            Log.e(TAG, "PCM playback worker failed", e)
        } finally {
            track?.let(::releaseTrack)
            synchronized(playbackLock) {
                if (activePlaybackTrack === track) activePlaybackTrack = null
                if (playbackGeneration == generation) {
                    playbackQueue = null
                    playbackJob = null
                    abandonAudioFocus()
                }
            }
        }
    }

    private fun createAudioTrack(): AudioTrack? {
        requestLoudspeakerAudioFocus()
        val minBuffer = AudioTrack.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_OUT_STEREO,
            AudioFormat.ENCODING_PCM_16BIT)
        if (minBuffer <= 0) return null
        return AudioTrack.Builder()
            // Media usage is intentional: several Android vendors force
            // USAGE_VOICE_COMMUNICATION through the earpiece even after an explicit
            // speaker route request. Media speech reliably uses the loudspeaker.
            .setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) // Changed: avoid communication-style audio DSP for playback
                .build())
            .setAudioFormat(AudioFormat.Builder().setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                .setSampleRate(SAMPLE_RATE).setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build())
            .setBufferSizeInBytes(maxOf(minBuffer, PCM_FRAME_BYTES * 4))
            .setTransferMode(AudioTrack.MODE_STREAM)
            .build().also { track ->
                if (track.state != AudioTrack.STATE_INITIALIZED) { track.release(); return null }
                track.setVolume(receiveVolume)
                track.play()
                Log.i(TAG, "Media AudioTrack started with system routing routed=${track.routedDevice?.productName}")
            }
    }

    fun stopReceivePlayback(reason: String = "requested") {
        val job = synchronized(playbackLock) {
            playbackGeneration++
            playbackQueue?.close()
            playbackQueue = null
            playbackJob.also { playbackJob = null }
        }
        job?.cancel()
        Log.i(TAG, "Receive playback stopped: $reason diagnostics=${receiveDiagnostics()}")
    }

    private fun releaseTrack(track: AudioTrack) {
        synchronized(playbackLock) {
            if (activePlaybackTrack === track) activePlaybackTrack = null
        }
        try { track.stop() } catch (_: Exception) {}
        track.release()
    }

    private fun requestLoudspeakerAudioFocus() {
        if (hasAudioFocus) return
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
                    .setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) // Changed: avoid communication-style audio DSP for playback
                        .build())
                    .setOnAudioFocusChangeListener {}.build()
                hasAudioFocus = audioManager?.requestAudioFocus(audioFocusRequest!!) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
            } else @Suppress("DEPRECATION") {
                hasAudioFocus = audioManager?.requestAudioFocus(null, AudioManager.STREAM_MUSIC,
                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
            }
            // Do not enter MODE_IN_COMMUNICATION for receive playback. On some vendor
            // ROMs that mode wins over setPreferredDevice and routes media to earpiece.
            if (audioManager?.mode != AudioManager.MODE_NORMAL) {
                audioManager?.mode = AudioManager.MODE_NORMAL
            }
            ownsCommunicationMode = false
        } catch (e: SecurityException) {
            // Audio focus/routing may be rejected by a vendor ROM; playback can still proceed.
            hasAudioFocus = false
            audioFocusRequest = null
            Log.w(TAG, "Audio focus request denied", e)
        }
    }

    private fun forceLoudspeaker() {
        // Playback uses USAGE_MEDIA and normally follows the system's active route
        // (wired headset, USB, Bluetooth, then speaker). Clearing a stale explicit
        // communication device is enough; pinning AudioTrack to the built-in speaker
        // would prevent headset and Bluetooth playback.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            audioManager?.clearCommunicationDevice()
        } else {
            @Suppress("DEPRECATION")
            audioManager?.isSpeakerphoneOn = false
        }
    }

    private fun abandonAudioFocus() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) audioFocusRequest?.let { audioManager?.abandonAudioFocusRequest(it) }
        else @Suppress("DEPRECATION") audioManager?.abandonAudioFocus(null)
        audioFocusRequest = null; hasAudioFocus = false
        forceLoudspeaker()
        if (ownsCommunicationMode) { audioManager?.mode = AudioManager.MODE_NORMAL; ownsCommunicationMode = false }
    }

    private suspend fun captureLoop() {
        while (isCapturing) {
            val read = audioRecord?.read(recordBuffer, 0, FRAME_SIZE) ?: break
            if (read != FRAME_SIZE) continue
            if (calculateRMS(recordBuffer) > SPEAKING_THRESHOLD && !isMuted) setSelfSpeaking(true)
            if (!isMuted) {
                val encoded = opusEncoder?.encode(recordBuffer) ?: continue
                val error = onVoiceData?.invoke(encoded, 4).orEmpty()
                if (error.isNotEmpty()) {
                    isCapturing = false
                    setSelfSpeaking(false)
                    onVoiceSendError?.invoke(error)
                    break
                }
            }
        }
    }

    private fun calculateRMS(pcm: ShortArray): Float {
        var sum = 0.0
        for (sample in pcm) { val value = sample.toFloat() / Short.MAX_VALUE; sum += value * value }
        return sqrt(sum / pcm.size).toFloat()
    }

    private fun setSelfSpeaking(speaking: Boolean) {
        if (speaking) {
            onSelfSpeaking?.invoke(true)
            selfSpeakingJob?.cancel()
            selfSpeakingJob = scope.launch { delay(SPEAKING_TIMEOUT_MS); onSelfSpeaking?.invoke(false) }
        } else {
            selfSpeakingJob?.cancel(); selfSpeakingJob = null; onSelfSpeaking?.invoke(false)
        }
    }

    private fun setupNoiseSuppressor() {
        if (denoiseEnabled && NoiseSuppressor.isAvailable()) try {
            noiseSuppressor = audioRecord?.let { NoiseSuppressor.create(it.audioSessionId)?.apply { enabled = true } }
        } catch (e: Exception) { Log.w(TAG, "Failed to create NoiseSuppressor", e) }
    }

    fun destroy() {
        stopCapture()
        stopReceivePlayback("destroy")
        selfSpeakingJob?.cancel(); selfSpeakingJob = null
        Log.d(TAG, "VoiceService destroyed")
    }

    /**
     * 对 PCM16 字节数组应用增益，clamp 到 int16 范围防止溢出。
     * PCM 格式：小端序 interleaved stereo。
     */
    private fun applyPcmGain(pcm: ByteArray, gain: Float): ByteArray {
        if (gain == 1.0f) return pcm
        val result = pcm.copyOf()
        for (i in result.indices step 2) {
            // 小端序：低字节在前
            val sample = ((result[i + 1].toInt() shl 8) or (result[i].toInt() and 0xFF)).toShort()
            val gained = (sample.toFloat() * gain).toInt().coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt())
            result[i] = (gained and 0xFF).toByte()
            result[i + 1] = (gained shr 8).toByte()
        }
        return result
    }
}
