package app.closer.data.remote

import app.closer.domain.model.DateIdea
import app.closer.domain.model.DateMatch
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.FieldValue
import com.google.firebase.firestore.FirebaseFirestore
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
 * Firestore data source for revealed mutual date matches.
 *
 * Path layout:
 * couples/{coupleId}/date_matches/{matchId}
 *
 * Each document contains:
 * {
 *   "dateIdeaId": "idea_123",
 *   "revealedAt": 12345,
 *   "matchedBy": ["userA", "userB"]
 * }
 *
 * Under E2EE the server can no longer read swipe content, so mutual-love
 * detection moved client-side: when a member records the second LOVE on an idea,
 * the client writes the match marker here (idempotent, keyed by dateIdeaId). A
 * Cloud Function then fires the "It's a match!" notification on create. Security
 * rules allow couple members to create (validated shape) but never update/delete.
 */
@Singleton
class FirestoreDateMatchDataSource @Inject constructor(private val db: FirebaseFirestore) {
    private fun matchesRef(coupleId: String) =
        db.collection(FirestoreCollections.COUPLES).document(coupleId)
            .collection(FirestoreCollections.Couples.DATE_MATCHES)

    /**
     * Create the match marker for [dateIdeaId] iff it doesn't already exist. Keyed by
     * dateIdeaId and guarded by a transaction so both partners loving near-simultaneously
     * (and repeated swipes) never produce a duplicate. `fcmNotified` lets the notify
     * function claim the push exactly once.
     */
    suspend fun createMatchIfAbsent(coupleId: String, dateIdeaId: String, matchedBy: List<String>) {
        val ref = matchesRef(coupleId).document(dateIdeaId)
        db.runTransaction<Unit> { tx ->
            if (!tx.get(ref).exists()) {
                tx.set(
                    ref,
                    mapOf(
                        "dateIdeaId" to dateIdeaId,
                        "revealedAt" to FieldValue.serverTimestamp(),
                        "matchedBy" to matchedBy,
                        "fcmNotified" to false
                    )
                )
            }
        }.await()
    }

    suspend fun findMatchByDateIdeaId(coupleId: String, dateIdeaId: String): DateMatch? {
        val snap = matchesRef(coupleId)
            .whereEqualTo("dateIdeaId", dateIdeaId)
            .limit(1)
            .queryAwait()
        return snap.documents.firstOrNull()?.toDateMatch(coupleId)
    }

    fun observeMatches(coupleId: String): Flow<List<DateMatch>> = callbackFlow {
        val listener = matchesRef(coupleId)
            .orderBy("revealedAt", com.google.firebase.firestore.Query.Direction.DESCENDING)
            .addSnapshotListener { snap, err ->
                if (err != null || snap == null) return@addSnapshotListener
                trySend(snap.documents.mapNotNull { it.toDateMatch(coupleId) })
            }
        awaitClose { listener.remove() }
    }

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

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

    // ─── Mapper ──────────────────────────────────────────────────────────────

    @Suppress("UNCHECKED_CAST")
    private fun DocumentSnapshot.toDateMatch(coupleId: String): DateMatch? {
        val dateIdeaId = getString("dateIdeaId") ?: return null
        return DateMatch(
            id = id,
            coupleId = coupleId,
            dateIdeaId = dateIdeaId,
            revealedAt = getTimestamp("revealedAt")?.toDate()?.time ?: 0L,
            matchedBy = (get("matchedBy") as? List<String>) ?: emptyList()
        )
    }
}
