package chat.pyramid.pyramid

import android.os.Handler
import android.os.Looper
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

class PushService : FirebaseMessagingService() {

    override fun onMessageReceived(message: RemoteMessage) {
        val data   = message.data
        val roomId = data["room_id"] ?: run {
            Log.d(TAG, "no room_id in payload, skipping")
            return
        }

        val prefs = getSharedPreferences("FlutterSharedPreferences", MODE_PRIVATE)

        // Skip if the Dart app is actively syncing (wrote a heartbeat < 20 s ago).
        val heartbeat = try {
            prefs.getLong("flutter.notif_app_heartbeat", 0L)
                .takeIf { it > 0L }
                ?: prefs.getInt("flutter.notif_app_heartbeat", 0).toLong()
        } catch (_: Exception) { 0L }
        if (System.currentTimeMillis() - heartbeat < 20_000L) {
            Log.d(TAG, "app heartbeat fresh, skipping FCM notification")
            return
        }

        val eventId = data["event_id"]
        val notifId = NotificationHelper.stableId(roomId)
        val hs      = (prefs.getString("flutter.notif_homeserver",   "") ?: "").trimEnd('/')
        val token   = (prefs.getString("flutter.notif_access_token", "") ?: "")

        // Fetch sender + room name on a background thread (main thread forbids I/O).
        var title = "Pyramid"
        if (hs.isNotEmpty() && token.isNotEmpty() && eventId != null) {
            val latch = CountDownLatch(1)
            Thread {
                try { title = fetchTitle(hs, token, roomId, eventId) ?: "Pyramid" }
                finally { latch.countDown() }
            }.start()
            latch.await(12L, TimeUnit.SECONDS)
        }

        Log.d(TAG, "handling push: title=$title roomId=$roomId")

        val engine = FlutterEngineCache.getInstance().get("main")
        if (engine != null) {
            // App backgrounded but alive — show placeholder immediately, then the
            // warm main client decrypts and updates it (fast, barely visible).
            NotificationHelper.show(this, title, "Neue Nachricht", roomId, notifId)
            if (eventId != null) {
                val capturedRoomId  = roomId
                val capturedEventId = eventId
                val capturedNotifId = notifId
                Handler(Looper.getMainLooper()).post {
                    try {
                        MethodChannel(engine.dartExecutor.binaryMessenger,
                                      "chat.pyramid.pyramid/install")
                            .invokeMethod("decryptAndUpdateNotification", mapOf(
                                "room_id"  to capturedRoomId,
                                "event_id" to capturedEventId,
                                "notif_id" to capturedNotifId,
                            ))
                        Log.d(TAG, "decryptAndUpdateNotification invoked (main engine) for $capturedRoomId")
                    } catch (e: Exception) {
                        Log.w(TAG, "decryptAndUpdateNotification failed: ${e.message}")
                    }
                }
            }
        } else if (eventId != null) {
            // App killed — decrypt FIRST via the background engine, then show the
            // real content directly. The "Neue Nachricht" placeholder is only a
            // fallback if decryption takes too long or fails.
            Log.d(TAG, "main engine gone — decrypt-first via background engine for $roomId")
            BgEngine.scheduleFallback(this, title, roomId, notifId, 6_000L)
            BgEngine.run(this, "decryptAndShow", mapOf(
                "room_id"  to roomId,
                "event_id" to eventId,
                "notif_id" to notifId,
            ))
        } else {
            // No event id to decrypt — just show the placeholder.
            NotificationHelper.show(this, title, "Neue Nachricht", roomId, notifId)
        }
    }

    override fun onNewToken(token: String) {
        Log.d(TAG, "FCM token refreshed — storing for Dart to re-register pusher")
        getSharedPreferences("FlutterSharedPreferences", MODE_PRIVATE)
            .edit()
            .putString("flutter.fcm_pending_token", token)
            .apply()
    }

    // ─── HTTP helpers ─────────────────────────────────────────────────────────

    private fun fetchTitle(
        hs: String, token: String, roomId: String, eventId: String
    ): String? {
        return try {
            val encRoom  = java.net.URLEncoder.encode(roomId,  "UTF-8")
            val encEvent = java.net.URLEncoder.encode(eventId, "UTF-8")

            val event = httpGet("$hs/_matrix/client/v3/rooms/$encRoom/event/$encEvent", token)
                ?: return null

            val senderId  = event.optString("sender")
            var senderName = senderId.split(":").firstOrNull()?.removePrefix("@") ?: ""

            if (senderId.isNotEmpty()) {
                val encSender = java.net.URLEncoder.encode(senderId, "UTF-8")
                val member = httpGet(
                    "$hs/_matrix/client/v3/rooms/$encRoom/state/m.room.member/$encSender", token)
                val name = member?.optString("displayname")
                if (!name.isNullOrEmpty()) senderName = name
            }

            val nameState = httpGet("$hs/_matrix/client/v3/rooms/$encRoom/state/m.room.name", token)
            val roomName  = nameState?.optString("name")

            return if (!roomName.isNullOrEmpty()) "$senderName · $roomName"
                   else senderName.ifEmpty { null }
        } catch (e: Exception) {
            Log.w(TAG, "fetchTitle failed: ${e.message}")
            null
        }
    }

    private fun httpGet(url: String, token: String): JSONObject? {
        var conn: HttpURLConnection? = null
        return try {
            conn = (URL(url).openConnection() as HttpURLConnection).apply {
                requestMethod = "GET"
                setRequestProperty("Authorization", "Bearer $token")
                connectTimeout = 8_000
                readTimeout    = 8_000
            }
            if (conn.responseCode != 200) return null
            JSONObject(conn.inputStream.bufferedReader().readText())
        } catch (_: Exception) {
            null
        } finally {
            conn?.disconnect()
        }
    }

    companion object {
        private const val TAG = "PYRAMID-PUSH"
    }
}
