package app.closer.data.remote

import app.closer.crypto.CoupleEncryptionManager
import app.closer.crypto.FieldEncryptor
import app.closer.domain.model.DateSwipe
import app.closer.domain.model.SwipeAction
import com.google.crypto.tink.Aead
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.SetOptions
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.suspendCancellableCoroutine
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
 * Firestore data source for per-couple, per-date swipe state.
 *
 * Path layout:
 * couples/{coupleId}/date_swipes/{dateId}
 *
 * Each document stores a map keyed by userId. The `action` is E2E-encrypted with
 * the couple key (like every other game) so the server can never read a partner's
 * date preferences; only `swipedAt` stays plaintext.
 * {
 *   "actions": {
 *     "userA": { "action": "enc:v1:…", "swipedAt": 12345 },
 *     "userB": { "action": "enc:v1:…", "swipedAt": 12346 }
 *   }
 * }
 *
 * Security rules ensure only couple members can read/write, each user can only
 * write their own action entry, and the action must be ciphertext. Match detection
 * is therefore done client-side (the server is blind to swipe content).
 */
@Singleton
class FirestoreDateSwipeDataSource @Inject constructor(
    private val db: FirebaseFirestore,
    private val encryptionManager: CoupleEncryptionManager,
    private val fieldEncryptor: FieldEncryptor
) {
    private fun swipesRef(coupleId: String) =
        db.collection(FirestoreCollections.COUPLES).document(coupleId)
            .collection(FirestoreCollections.Couples.DATE_SWIPES)

    suspend fun recordSwipe(coupleId: String, swipe: DateSwipe) {
        val aead = encryptionManager.requireAead(coupleId)
        val path = swipesRef(coupleId).document(swipe.dateIdeaId)
        val entry = mapOf(
            "action" to fieldEncryptor.encrypt(swipe.action.toFirestoreValue(), aead, coupleId),
            "swipedAt" to swipe.swipedAt
        )
        path.set(
            mapOf("actions" to mapOf(swipe.userId to entry)),
            SetOptions.merge()
        ).voidAwait()
    }

    /** Decrypt a stored action, tolerating legacy plaintext written before E2EE. */
    private fun decryptAction(raw: String?, aead: Aead?, coupleId: String): String {
        if (raw.isNullOrEmpty()) return ""
        return if (fieldEncryptor.isEncrypted(raw)) {
            fieldEncryptor.decrypt(raw, aead, coupleId) ?: ""
        } else {
            raw // legacy plaintext from before this migration
        }
    }

    suspend fun getSwipe(coupleId: String, dateIdeaId: String, userId: String): DateSwipe? {
        val aead = encryptionManager.aeadFor(coupleId)
        val snap = swipesRef(coupleId).document(dateIdeaId).getDoc()
        return snap.toDateSwipe(dateIdeaId, userId, aead, coupleId)
    }

    fun observeOwnSwipes(
        coupleId: String,
        userId: String,
        once: Boolean = false
    ): Flow<List<DateSwipe>> {
        return if (once) {
            kotlinx.coroutines.flow.flow {
                val swipes = getAllSwipesForUser(coupleId, userId)
                emit(swipes)
            }
        } else {
            callbackFlow {
                val listener = swipesRef(coupleId)
                    .addSnapshotListener { snap, err ->
                        if (err != null || snap == null) return@addSnapshotListener
                        val aead = encryptionManager.aeadFor(coupleId)
                        val swipes = snap.documents.mapNotNull { it.toDateSwipe(it.id, userId, aead, coupleId) }
                        trySend(swipes)
                    }
                awaitClose { listener.remove() }
            }
        }
    }

    @Suppress("UNCHECKED_CAST")
    private suspend fun getAllSwipesForUser(coupleId: String, userId: String): List<DateSwipe> {
        val aead = encryptionManager.aeadFor(coupleId)
        val snap = swipesRef(coupleId).getQuery()
        return snap.documents.mapNotNull { it.toDateSwipe(it.id, userId, aead, coupleId) }
    }

    private suspend fun com.google.firebase.firestore.CollectionReference.getQuery() =
        suspendCancellableCoroutine { cont ->
            get()
                .addOnSuccessListener { cont.resume(it) }
                .addOnFailureListener { cont.resumeWithException(it) }
        }

    suspend fun getAllSwipesForDate(coupleId: String, dateIdeaId: String): List<DateSwipe> {
        val aead = encryptionManager.aeadFor(coupleId)
        val snap = swipesRef(coupleId).document(dateIdeaId).getDoc()
        @Suppress("UNCHECKED_CAST")
        val actions = snap.get("actions") as? Map<String, Map<String, Any>> ?: emptyMap()
        return actions.map { (uid, data) ->
            DateSwipe(
                dateIdeaId = dateIdeaId,
                userId = uid,
                action = SwipeAction.fromFirestoreValue(decryptAction(data["action"] as? String, aead, coupleId)),
                swipedAt = (data["swipedAt"] as? Number)?.toLong() ?: 0L
            )
        }
    }

    // ─── Coroutine helpers ───────────────────────────────────────────────────

    private suspend fun com.google.firebase.firestore.DocumentReference.getDoc(): DocumentSnapshot =
        suspendCancellableCoroutine { cont ->
            get()
                .addOnSuccessListener { cont.resume(it) }
                .addOnFailureListener { cont.resumeWithException(it) }
        }

    private suspend fun com.google.android.gms.tasks.Task<Void>.voidAwait() =
        suspendCancellableCoroutine<Unit> { cont ->
            addOnSuccessListener { cont.resume(Unit) }
            addOnFailureListener { cont.resumeWithException(it) }
        }

    // ─── Mappers ─────────────────────────────────────────────────────────────

    @Suppress("UNCHECKED_CAST")
    private fun DocumentSnapshot.toDateSwipe(
        dateIdeaId: String,
        userId: String,
        aead: Aead?,
        coupleId: String
    ): DateSwipe? {
        val actions = get("actions") as? Map<String, Map<String, Any>> ?: return null
        val data = actions[userId] ?: return null
        return DateSwipe(
            dateIdeaId = dateIdeaId,
            userId = userId,
            action = SwipeAction.fromFirestoreValue(decryptAction(data["action"] as? String, aead, coupleId)),
            swipedAt = (data["swipedAt"] as? Number)?.toLong() ?: 0L
        )
    }
}
