package chat.pyramid.pyramid

import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel
import io.flutter.view.FlutterCallbackInformation

/**
 * Bootstraps a headless Flutter background engine running the
 * `notificationEngineMain` Dart entrypoint and forwards work items
 * (decrypt-and-show, send-reply) to it via a MethodChannel.
 *
 * Only used when the app is KILLED (no main FlutterEngine). The Dart side builds
 * a short-lived Matrix client against the same DB to decrypt / send. This is safe
 * because callers gate on the main app being dead — never two clients at once.
 */
object BgEngine {
    private const val TAG = "PYRAMID-BGENGINE"
    private const val CHANNEL = "chat.pyramid.pyramid/bg_engine"

    private var engine: FlutterEngine? = null
    private var channel: MethodChannel? = null
    private var ready = false
    private var appContext: Context? = null
    // Tasks queued until the Dart handler signals readiness.
    private val pending = mutableListOf<Pair<String, Map<String, Any?>>>()
    // Pending "Neue Nachricht" fallback placeholders, keyed by notifId. Shown
    // only if decryption doesn't replace them in time; cancelled by showDecrypted.
    private val fallbackHandler = Handler(Looper.getMainLooper())
    private val fallbacks = mutableMapOf<Int, Runnable>()

    /** Schedule a placeholder shown after [delayMs] unless decryption beats it. */
    fun scheduleFallback(context: Context, title: String, roomId: String, notifId: Int, delayMs: Long) {
        val ctx = context.applicationContext
        appContext = ctx
        fallbackHandler.post {
            cancelFallback(notifId)
            val r = Runnable {
                fallbacks.remove(notifId)
                NotificationHelper.show(ctx, title, "Neue Nachricht", roomId, notifId)
                Log.d(TAG, "fallback placeholder shown for $roomId (decrypt too slow)")
            }
            fallbacks[notifId] = r
            fallbackHandler.postDelayed(r, delayMs)
        }
    }

    private fun cancelFallback(notifId: Int) {
        fallbacks.remove(notifId)?.let { fallbackHandler.removeCallbacks(it) }
    }

    /** Enqueue a task; boots the engine on first use. Must run on the main thread. */
    fun run(context: Context, method: String, args: Map<String, Any?>) {
        appContext = context.applicationContext
        Handler(Looper.getMainLooper()).post {
            try {
                ensureEngine(context.applicationContext)
                if (ready) {
                    channel?.invokeMethod(method, args)
                } else {
                    pending.add(method to args)
                }
            } catch (e: Exception) {
                Log.w(TAG, "run failed: ${e.message}")
            }
        }
    }

    private fun ensureEngine(context: Context) {
        if (engine != null) return

        val prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
        val handle = try {
            prefs.getLong("flutter.bg_engine_handle", 0L)
        } catch (_: Exception) {
            0L
        }
        if (handle == 0L) {
            Log.w(TAG, "no bg_engine_handle stored — cannot start background engine")
            return
        }

        // The Flutter native library must be loaded BEFORE looking up the
        // callback — nativeLookupCallbackInformation is a native method and
        // throws UnsatisfiedLinkError otherwise.
        val loader = FlutterInjector.instance().flutterLoader()
        loader.startInitialization(context)
        loader.ensureInitializationComplete(context, null)

        val cbInfo = FlutterCallbackInformation.lookupCallbackInformation(handle)
        if (cbInfo == null) {
            Log.w(TAG, "callback info not found for handle $handle")
            return
        }

        val eng = FlutterEngine(context)
        engine = eng

        val ch = MethodChannel(eng.dartExecutor.binaryMessenger, CHANNEL)
        channel = ch
        ch.setMethodCallHandler { call, result ->
            when (call.method) {
                "bgEngineReady" -> {
                    ready = true
                    Log.d(TAG, "Dart background engine ready — flushing ${pending.size} task(s)")
                    pending.forEach { (m, a) -> ch.invokeMethod(m, a) }
                    pending.clear()
                    result.success(null)
                }
                // Dart finished decrypting — show the real content using the SAME
                // native notification (keeps the reliable MainActivity reply action).
                "showDecrypted" -> {
                    val title   = call.argument<String>("title")  ?: ""
                    val body    = call.argument<String>("body")   ?: "Neue Nachricht"
                    val roomId  = call.argument<String>("room_id") ?: ""
                    val notifId = call.argument<Int>("notif_id") ?: NotificationHelper.stableId(roomId)
                    val ctx = appContext
                    if (ctx != null && roomId.isNotEmpty()) {
                        // Beat the fallback placeholder to it — show decrypted directly.
                        cancelFallback(notifId)
                        NotificationHelper.show(ctx, title, body, roomId, notifId)
                        Log.d(TAG, "showDecrypted → shown decrypted notification for $roomId")
                    }
                    result.success(null)
                }
                else -> result.notImplemented()
            }
        }

        eng.dartExecutor.executeDartCallback(
            DartExecutor.DartCallback(context.assets, loader.findAppBundlePath(), cbInfo)
        )
        Log.d(TAG, "background engine started")
    }
}
