package app.chanora.chanora_flutter

import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log

/**
 * Manages Android audio focus (requestAudioFocus / abandonAudioFocus) for
 * the Chanora voice session.
 *
 * Trace: SDD-109 (Android Audio Focus)
 *
 * ## Lifecycle
 *
 *  1. [start] — called by the Rust audio engine (via JNI) after the Oboe
 *     voice streams are opened. Requests `AUDIOFOCUS_GAIN` for
 *     `USAGE_VOICE_COMMUNICATION` / `CONTENT_TYPE_SPEECH` and registers
 *     an `OnAudioFocusChangeListener`.
 *  2. Focus-change callbacks are forwarded to the Rust engine via
 *     `publishFocusChange(int)`, a JNI function declared in
 *     `crates/chanora_audio/src/android_voice_unit.rs`.
 *  3. [stop] — called by the Rust engine on voice stop. Abandons focus
 *     and clears the listener.
 *
 * ## Thread model
 *
 * `start` / `stop` are called from a tokio worker thread (via JNI), not
 * the Android main thread. The `AudioManager` API is thread-safe.
 * `OnAudioFocusChangeListener` callbacks arrive on the main thread; we
 * forward to Rust via JNI which attaches the calling thread to the JVM.
 */
internal class AndroidAudioFocusController {

    companion object {
        private const val TAG = "ChanoraAudioFocus"

        /**
         * JNI entry point implemented in
         * `crates/chanora_audio/src/android_voice_unit.rs`.
         *
         * Kotlin calls this from [OnAudioFocusChangeListener] to forward
         * the focus-change integer to the Rust engine's BackendEvent channel.
         */
        @JvmStatic
        external fun publishFocusChange(state: Int)

        /**
         * Start audio focus management.
         *
         * Called from Rust via JNI after voice unit start.
         * Idempotent: repeated calls against an already-started instance
         * are silently ignored.
         */
        @JvmStatic
        fun start(context: Context) {
            if (focusRequested) {
                Log.d(TAG, "start() called but focus already held; no-op")
                return
            }
            requestFocus(context)
        }

        /**
         * Stop audio focus management.
         *
         * Called from Rust via JNI on voice stop.
         * Idempotent: safe to call when no focus is held.
         */
        @JvmStatic
        fun stop(context: Context) {
            if (!focusRequested) {
                Log.d(TAG, "stop() called but no focus held; no-op")
                return
            }
            abandonFocus(context)
        }

        private var focusRequested: Boolean = false
        private var focusRequestHandle: AudioFocusRequest? = null

        private val mainHandler = Handler(Looper.getMainLooper())

        private val audioFocusListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
            Log.i(TAG, "onAudioFocusChange: $focusChange")
            try {
                publishFocusChange(focusChange)
            } catch (t: Throwable) {
                Log.w(TAG, "publishFocusChange JNI failed: ${t.message}", t)
            }
        }

        private fun requestFocus(context: Context) {
            val am = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
            if (am == null) {
                Log.e(TAG, "AudioManager unavailable; cannot request audio focus")
                return
            }
            try {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    val attr = AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        .build()
                    val request = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                        .setAudioAttributes(attr)
                        .setOnAudioFocusChangeListener(audioFocusListener, mainHandler)
                        .build()
                    val result = am.requestAudioFocus(request)
                    focusRequested = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
                    if (focusRequested) {
                        focusRequestHandle = request
                    }
                    Log.i(TAG, "requestAudioFocus result=$result granted=$focusRequested")
                } else {
                    @Suppress("DEPRECATION")
                    val result = am.requestAudioFocus(
                        audioFocusListener,
                        AudioManager.STREAM_VOICE_CALL,
                        AudioManager.AUDIOFOCUS_GAIN,
                    )
                    focusRequested = result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
                    Log.i(TAG, "requestAudioFocus result=$result granted=$focusRequested")
                }
            } catch (e: SecurityException) {
                Log.e(TAG, "requestAudioFocus denied by platform: ${e.message}", e)
                focusRequested = false
            }
        }

        private fun abandonFocus(context: Context) {
            val am = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
            if (am == null) {
                Log.e(TAG, "AudioManager unavailable; cannot abandon audio focus")
                focusRequested = false
                focusRequestHandle = null
                return
            }
            try {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    val handle = focusRequestHandle
                    focusRequestHandle = null
                    if (handle != null) {
                        am.abandonAudioFocusRequest(handle)
                    }
                } else {
                    @Suppress("DEPRECATION")
                    am.abandonAudioFocus(audioFocusListener)
                }
                Log.i(TAG, "abandonAudioFocus dispatched")
            } catch (e: SecurityException) {
                Log.w(TAG, "abandonAudioFocus failed: ${e.message}", e)
            }
            focusRequested = false
            focusRequestHandle = null
        }
    }
}
