package app.chanora.chanora_flutter

import android.content.Context
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

/**
 * Android audio lifecycle controller.
 *
 * Mirrors the iOS `AppDelegate` audio lifecycle events — route changes,
 * device connectivity changes, and app lifecycle transitions — and
 * forwards them to the Dart layer over a MethodChannel. The Dart side
 * dispatches these to the Rust bridge, the same pattern as
 * `_wireIosAudioLifecycle()` in `main.dart`.
 *
 * Trace: SDD-111 (MobileVoiceAudioBackend cross-platform)
 *
 * ## Events published
 *
 *  `routeChange`        — audio device connected/disconnected (payload:
 *                          `{"routeType": String}`). Mimics iOS
 *                          `handleRouteChange`.
 *  `interruptionBegan`  — audio interruption started (e.g. phone call).
 *  `interruptionEnded`  — audio interruption ended, with `shouldResume`.
 *  `appDidEnterBackground` — app moved to background.
 *  `appWillEnterForeground` — app returned to foreground.
 *  `mediaServicesReset` — equivalent of iOS media services reset
 *                          (Android: audio devices changed significantly).
 *
 * ## Thread model
 *
 * All callbacks from `AudioDeviceCallback` arrive on the main thread
 * (registered with the main `Handler`). App lifecycle observation is
 * driven by the Flutter `AppLifecycleListener` on the Dart side;
 * this controller only owns the audio-device side. The Flutter side
 * is responsible for wiring lifecycle and forwarding events to Rust.
 */
internal class AndroidAudioLifecycleController(
    private val context: Context,
) {
    private val audioManager: AudioManager =
        context.applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
    private val mainHandler = Handler(Looper.getMainLooper())
    private var callbackRegistered = false

    private var currentRouteFingerprint: String? = null

    private val audioDeviceCallback = object : AudioDeviceCallback() {
        override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) {
            notifyRouteChange(addedDevices.firstOrNull())
        }

        override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
            notifyRouteChange(removedDevices.firstOrNull())
        }
    }

    private var channel: MethodChannel? = null

    /**
     * Attach to [flutterEngine]'s binary messenger.
     *
     * Creates a MethodChannel named `chanora/android_audio_lifecycle`
     * and starts observing audio device changes.
     */
    fun attach(flutterEngine: FlutterEngine) {
        channel = MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            CHANNEL_NAME,
        )
        startObservingAudioDevices()
        Log.i(TAG, "attached to flutter engine")
    }

    /**
     * Detach from the Flutter engine and stop observing.
     */
    fun detach() {
        stopObservingAudioDevices()
        channel?.setMethodCallHandler(null)
        channel = null
        Log.i(TAG, "detached")
    }

    /**
     * Call from `Activity.onResume` to re-evaluate the current route.
     */
    fun onResume() {
        notifyRouteChange(null)
    }

    /**
     * Call from `Activity.onDestroy` to tear down.
     */
    fun onDestroy() {
        detach()
    }

    private fun startObservingAudioDevices() {
        if (callbackRegistered) return
        audioManager.registerAudioDeviceCallback(audioDeviceCallback, mainHandler)
        callbackRegistered = true
        Log.d(TAG, "audio device observer started")
    }

    private fun stopObservingAudioDevices() {
        if (!callbackRegistered) return
        audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
        callbackRegistered = false
        Log.d(TAG, "audio device observer stopped")
    }

    private fun notifyRouteChange(
        device: AudioDeviceInfo?,
    ) {
        val routeType = classifyCurrentRoute(device)
        val routeFingerprint = buildRouteFingerprint(routeType)
        if (routeFingerprint == currentRouteFingerprint) {
            return
        }
        currentRouteFingerprint = routeFingerprint
        Log.i(TAG, "route changed to: $routeType fingerprint=$routeFingerprint")
        channel?.invokeMethod(
            "handleRouteChange",
            mapOf("routeType" to routeType),
        )
    }

    private fun buildRouteFingerprint(routeType: String): String {
        val selectedCommunicationId =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                audioManager.communicationDevice?.id?.toString().orEmpty()
            } else {
                ""
            }
        val inputs = audioManager
            .getDevices(AudioManager.GET_DEVICES_INPUTS)
            .map { "${it.id}:${it.type}:${it.productName?.toString()?.trim().orEmpty()}" }
            .sorted()
            .joinToString("|")
        val outputs = audioManager
            .getDevices(AudioManager.GET_DEVICES_OUTPUTS)
            .map { "${it.id}:${it.type}:${it.productName?.toString()?.trim().orEmpty()}" }
            .sorted()
            .joinToString("|")
        return "$routeType#$selectedCommunicationId#$inputs#$outputs"
    }

    /**
     * Classify the current audio output route into a stable string
     * matching the iOS route-classification schema so the Dart-side
     * parser (`_parseBridgeAudioRoute`) works identically across
     * platforms.
     */
    private fun classifyCurrentRoute(specificDevice: AudioDeviceInfo?): String {
        // If a specific device was added/removed, prefer its type.
        if (specificDevice != null) {
            return classifyDevice(specificDevice)
        }

        // Otherwise, classify based on the current output devices.
        val outputs = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
        if (outputs.isEmpty()) return "Unknown"

        // Prefer wired/Bluetooth headset if connected.
        for (d in outputs) {
            val t = classifyDevice(d)
            when (t) {
                "WiredHeadset", "BluetoothHfp", "BluetoothA2dp", "UsbHeadset" -> return t
                else -> {}
            }
        }
        // Fall back to the first output device classification.
        val first = classifyDevice(outputs.first())
        return when (first) {
            "Speaker" -> "Speaker"
            "Earpiece" -> "Earpiece"
            else -> "Speaker" // Default to Speaker for unknown outputs.
        }
    }

    private fun classifyDevice(d: AudioDeviceInfo): String = when (d.type) {
        AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "Earpiece"
        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "Speaker"
        AudioDeviceInfo.TYPE_WIRED_HEADSET,
        AudioDeviceInfo.TYPE_WIRED_HEADPHONES -> "WiredHeadset"
        AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "BluetoothHfp"
        AudioDeviceInfo.TYPE_BLUETOOTH_A2DP -> "BluetoothA2dp"
        AudioDeviceInfo.TYPE_BLE_HEADSET,
        AudioDeviceInfo.TYPE_BLE_SPEAKER -> if (Build.VERSION.SDK_INT >= 31) "BluetoothHfp" else "BluetoothA2dp"
        AudioDeviceInfo.TYPE_USB_HEADSET -> "UsbHeadset"
        AudioDeviceInfo.TYPE_HDMI -> "Hdmi"
        else -> {
            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
                d.type == AudioDeviceInfo.TYPE_BLE_BROADCAST
            ) {
                "BluetoothA2dp"
            } else {
                "Unknown"
            }
        }
    }

    companion object {
        private const val TAG = "ChanoraAudioLifecycle"

        /**
         * MethodChannel name for Android audio lifecycle events.
         * Mirrors `chanora/ios_audio_lifecycle` on iOS.
         */
        const val CHANNEL_NAME: String = "chanora/android_audio_lifecycle"
    }
}
