package app.chanora.chanora_flutter

import android.content.Context
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.View
import app.chanora.chanora_flutter.AndroidPermissionRequester
import app.chanora.chanora_flutter.BackIntentBridge
import app.chanora.chanora_flutter.MethodChannels
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel

/**
 * Host activity for the Chanora Flutter shell.
 *
 * Trace: SDD-105 (AndroidJniBootstrap), SDD-028 (Android lifecycle wiring),
 *        SDD-106 (AndroidPermissionRequester), DEC-004 (single JNI bootstrap path).
 *
 * Note (SDD-105):
 *   The `System.loadLibrary("chanora_bridge")` call previously lived in this
 *   activity's companion-object initialiser. It has been moved to
 *   [ChanoraApplication.onCreate] so the native library — and therefore
 *   `JNI_OnLoad`'s `JavaVM*` capture — is available before any plugin or
 *   background isolate touches the bridge. See ChanoraApplication.kt and
 *   the AndroidManifest <application android:name> declaration owned by
 *   Wave 2B-1.
 */
class MainActivity : FlutterActivity() {

    companion object {
        /**
         * JNI entry point implemented in `chanora_bridge::android_init`.
         * Initialises `ndk_context` with our Activity so the direct Oboe
         * backend can find Android audio services when `chanora_audio`
         * starts the capture / playback streams.
         *
         * Trace: SDD-105 (AndroidJniBootstrap). Signature must remain stable;
         * the Rust side declares the matching `extern "system"` symbol.
         */
        @JvmStatic
        external fun initChanoraContext(context: Context)

        /**
         * JNI entry point implemented in `chanora_bridge::permission_jni`.
         * Forwards a resolved Android runtime-permission state into the
         * Rust bridge, which (a) clamps the audio engine's transmit
         * selector when `permission == "android.permission.RECORD_AUDIO"`
         * and the state is anything other than `Granted`, and (b)
         * broadcasts a `BridgeEvent::PermissionState` so the Dart UI
         * observes the authoritative state alongside the existing
         * MethodChannel.
         *
         * The Rust side wraps the body in `catch_unwind` so a panic in
         * the bridge never unwinds into the JVM.
         *
         * Trace: SDD-106 §5, §6; SRS-209. Signature must remain stable;
         * the Rust side declares the matching `extern "system"` symbol
         * `Java_app_chanora_chanora_1flutter_MainActivity_publishPermissionState`.
         */
        @JvmStatic
        external fun publishPermissionState(permission: String, state: String)
    }

    // SDD-106: Activity-bound permission requester. Nullable because it is
    // only constructed once the FlutterEngine is configured; lifecycle
    // callbacks (onResume / onRequestPermissionsResult) must null-guard.
    private var permissionRequester: AndroidPermissionRequester? = null

    // SDD-106: Retained so onResume / onDestroy can forward state changes
    // to Dart on MethodChannels.ANDROID_PERMISSIONS.
    private var permissionsChannel: MethodChannel? = null
    private var audioOutputChannel: MethodChannel? = null
    private var audioOutputEvents: EventChannel? = null
    private var audioOutputController: AndroidAudioOutputController? = null

    // SDD-111: Android audio lifecycle controller (route changes, device
    // add/remove, app lifecycle). Mirrors the iOS
    // `chanora/ios_audio_lifecycle` channel pattern.
    private var audioLifecycleController: AndroidAudioLifecycleController? = null

    // SDD-028 (M-3 strict-review fix): the back-intent bridge is owned
    // by this Activity instance, not a process-wide `object`. Constructed
    // in configureFlutterEngine and cleared in onDestroy after detach().
    private var backIntentBridge: BackIntentBridge? = null

    private fun permissionStateWireName(
        state: AndroidPermissionRequester.PermissionState,
    ): String = when (state) {
        AndroidPermissionRequester.PermissionState.Granted -> "Granted"
        AndroidPermissionRequester.PermissionState.Denied -> "Denied"
        AndroidPermissionRequester.PermissionState.PermanentlyDenied -> "PermanentlyDenied"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        window.navigationBarColor = Color.rgb(255, 251, 254)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            window.decorView.systemUiVisibility =
                window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
        }
        // SDD-105: pass the Application context to the Rust side so the
        // audio engine can open device handles. Must run on the main thread
        // before any `chanora_audio` call from Dart. The native library is
        // already loaded by ChanoraApplication.onCreate at this point.
        initChanoraContext(applicationContext)
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        // SDD-028 / DEC-004: attach the Android -> Dart back-intent bridge to
        // the FlutterEngine's binary messenger as soon as the engine is
        // available. The bridge forwards hardware-back and intent-back events
        // into Dart's navigation stack.
        //
        // SDD-028 (M-3 strict-review fix): BackIntentBridge is now a class
        // instance owned by this Activity rather than a process-wide `object`,
        // eliminating the singleton-Activity-leak footgun.
        val bridge = BackIntentBridge()
        bridge.attach(this, flutterEngine.dartExecutor.binaryMessenger)
        backIntentBridge = bridge

        // SDD-106: Wired in fast-builder follow-up; closes Wave 2B-4 coordination gap.
        // Construct the permissions MethodChannel and the Activity-bound
        // requester, then wire stateChangeListener to forward resolved
        // PermissionState transitions to Dart.
        val channel = MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            MethodChannels.ANDROID_PERMISSIONS,
        )
        permissionsChannel = channel
        val requester = AndroidPermissionRequester()
        requester.stateChangeListener = { permission, state ->
            val stateName = permissionStateWireName(state)
            channel.invokeMethod(
                MethodChannels.METHOD_PERMISSION_STATE_CHANGED,
                mapOf(
                    "permission" to permission,
                    "state" to stateName,
                ),
            )
            // SDD-106 §5/§6: also forward the resolved state into the
            // Rust bridge so `TransmitModeSelector` clamps the
            // transmit gate authoritatively (independent of whether
            // the Dart UI has re-rendered yet). The Rust side is
            // panic-safe via `catch_unwind`; we still guard with
            // try/catch here so a `UnsatisfiedLinkError` (e.g. an
            // unexpected ABI mismatch) cannot crash MainActivity.
            // Trace: SDD-106 §5, §6; SRS-209.
            try {
                publishPermissionState(permission, stateName)
            } catch (t: Throwable) {
                android.util.Log.w(
                    "Chanora",
                    "publishPermissionState JNI hook failed: ${t.message}",
                    t,
                )
            }
        }
        permissionRequester = requester

        // SDD-106 §1, §3 (Dart-side integration follow-up): handle
        // outbound Dart -> Kotlin calls so the Flutter UI can drive the
        // runtime permission request and the settings deep-link. The
        // resolved state is still delivered asynchronously via
        // stateChangeListener -> METHOD_PERMISSION_STATE_CHANGED.
        //
        // Trace: SDD-106, SRS-209.
        channel.setMethodCallHandler { call, result ->
            when (call.method) {
                "requestRecordAudio" -> {
                    val r = permissionRequester
                    if (r != null) {
                        r.ensureRecordAudioPermission(this) { state ->
                            result.success(permissionStateWireName(state))
                        }
                    } else {
                        result.error(
                            "no_requester",
                            "AndroidPermissionRequester not bound",
                            null,
                        )
                    }
                }
                "requestStartupPermissions" -> {
                    val r = permissionRequester
                    if (r != null) {
                        r.ensureStartupPermissions(this) { states ->
                            result.success(
                                states.mapValues { (_, state) -> permissionStateWireName(state) }
                            )
                        }
                    } else {
                        result.error(
                            "no_requester",
                            "AndroidPermissionRequester not bound",
                            null,
                        )
                    }
                }
                "openAppSettings" -> {
                    val r = permissionRequester
                    if (r != null) {
                        r.openAppSettings(this)
                        result.success(null)
                    } else {
                        result.error(
                            "no_requester",
                            "AndroidPermissionRequester not bound",
                            null,
                        )
                    }
                }
                else -> result.notImplemented()
            }
        }

        val audioOutputController = AndroidAudioOutputController(applicationContext)
        this.audioOutputController = audioOutputController
        val audioOutputChannel = MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            MethodChannels.AUDIO_OUTPUT,
        )
        this.audioOutputChannel = audioOutputChannel
        audioOutputChannel.setMethodCallHandler(audioOutputController)
        val audioOutputEvents = EventChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            MethodChannels.AUDIO_OUTPUT_EVENTS,
        )
        this.audioOutputEvents = audioOutputEvents
        audioOutputEvents.setStreamHandler(audioOutputController)

        // SDD-111: attach the Android audio lifecycle controller
        // (route changes, device add/remove, interruptions).
        val lifecycleController = AndroidAudioLifecycleController(applicationContext)
        lifecycleController.attach(flutterEngine)
        audioLifecycleController = lifecycleController
    }

    override fun onResume() {
        super.onResume()
        // SDD-106: re-evaluate Android runtime permission state on resume and
        // forward the result to Dart via MethodChannel
        // "app.chanora/android_permissions". Must be null-safe: if the
        // requester is not yet wired we no-op rather than crashing.
        //
        // SDD-106: Wired in fast-builder follow-up; closes Wave 2B-4 coordination gap.
        val requester = permissionRequester
        if (requester != null) {
            // SDD-106: state already emitted by AndroidPermissionRequester via stateChangeListener; do NOT double-emit (M-4 fix)
            requester.onResume(this) { _ -> }
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
                requester.stateChangeListener?.invoke(
                    android.Manifest.permission.BLUETOOTH_CONNECT,
                    requester.currentState(this, android.Manifest.permission.BLUETOOTH_CONNECT),
                )
            }
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
                requester.stateChangeListener?.invoke(
                    android.Manifest.permission.POST_NOTIFICATIONS,
                    requester.currentState(this, android.Manifest.permission.POST_NOTIFICATIONS),
                )
            }
        }
        // SDD-111: re-evaluate the current audio route on resume.
        audioLifecycleController?.onResume()
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray,
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        // SDD-106: Wired in fast-builder follow-up; closes Wave 2B-4 coordination gap.
        // Forward the system result into the Activity-bound requester so its
        // pending callback resolves and stateChangeListener fires.
        permissionRequester?.handleRequestPermissionsResult(
            this,
            requestCode,
            permissions,
            grantResults,
        )
    }

    override fun onDestroy() {
        // SDD-028: detach the back-intent bridge before the activity is torn
        // down so the FlutterEngine's binary messenger isn't retained.
        //
        // SDD-028 (M-3 strict-review fix): drop the owning reference so the
        // bridge instance — and the Activity it retains — become eligible
        // for collection immediately after onDestroy.
        backIntentBridge?.detach()
        backIntentBridge = null
        // SDD-106: drop the Dart->Kotlin handler before nilling the
        // channel so a late invokeMethod from Dart cannot land on a
        // dangling requester reference.
        permissionsChannel?.setMethodCallHandler(null)
        audioOutputChannel?.setMethodCallHandler(null)
        audioOutputEvents?.setStreamHandler(null)
        audioOutputController?.detach()
        // SDD-111: detach the audio lifecycle controller.
        audioLifecycleController?.onDestroy()
        audioLifecycleController = null
        permissionRequester = null
        permissionsChannel = null
        audioOutputChannel = null
        audioOutputEvents = null
        audioOutputController = null
        super.onDestroy()
    }
}
