package app.closer.data.remote

import app.closer.crypto.CoupleEncryptionManager
import app.closer.crypto.FieldEncryptor
import app.closer.domain.model.DatePlan
import app.closer.domain.model.DatePlanPreference
import app.closer.domain.model.DatePlanStatus
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.SetOptions
import org.json.JSONArray
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.suspendCancellableCoroutine
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
 * Firestore data source for date plan preferences and plans.
 *
 * Path layout:
 * - couples/{coupleId}/date_plan_preferences/{userId} — per-partner preferences
 * - couples/{coupleId}/date_plans/{planId} — complete plans generated by the builder
 *
 * Local-first: Preferences stored in Room. Plans synced to Firestore when explicitly shared/scheduled.
 */
@Singleton
class FirestoreDatePlanDataSource @Inject constructor(
    private val db: FirebaseFirestore,
    private val encryptionManager: CoupleEncryptionManager,
    private val fieldEncryptor: FieldEncryptor
) {
    private fun preferencesRef(coupleId: String) =
        db.collection(FirestoreCollections.COUPLES).document(coupleId)
            .collection(FirestoreCollections.Couples.DATE_PLAN_PREFERENCES)

    private fun plansRef(coupleId: String) =
        db.collection(FirestoreCollections.COUPLES).document(coupleId)
            .collection(FirestoreCollections.Couples.DATE_PLANS)

    // ─── Preference methods ──────────────────────────────────────────────────

    suspend fun recordPreference(coupleId: String, preference: DatePlanPreference) {
        // Strict E2EE: user-entered details are encrypted. dateIdeaId stays plaintext
        // (queried via whereEqualTo); dates/timestamps stay plaintext (structural).
        val aead = encryptionManager.requireAead(coupleId)
        val path = preferencesRef(coupleId).document()
        val data = mapOf(
            "dateIdeaId" to preference.dateIdeaId,
            "preferredDate" to preference.preferredDate,
            "preferredTime" to fieldEncryptor.encrypt(preference.preferredTime, aead, coupleId),
            "budget" to fieldEncryptor.encrypt(preference.budget.toString(), aead, coupleId),
            "duration" to fieldEncryptor.encrypt(preference.duration, aead, coupleId),
            "createdAt" to preference.createdAt,
            "updatedAt" to preference.updatedAt
        )
        path.set(data, SetOptions.merge()).voidAwait()
    }

    suspend fun getPreference(coupleId: String, dateIdeaId: String): DatePlanPreference? {
        // Query for preferences on this date idea
        val snap = preferencesRef(coupleId)
            .whereEqualTo("dateIdeaId", dateIdeaId)
            .limit(1)
            .queryAwait()
        return snap.documents.firstOrNull()?.toDatePlanPreference(coupleId)
    }

    fun observePreferences(coupleId: String): Flow<List<DatePlanPreference>> {
        return callbackFlow {
            val listener = preferencesRef(coupleId)
                .addSnapshotListener { snap, err ->
                    if (err != null || snap == null) return@addSnapshotListener
                    trySend(snap.documents.mapNotNull { it.toDatePlanPreference(coupleId) })
                }
            awaitClose { listener.remove() }
        }
    }

    // ─── Plan methods ────────────────────────────────────────────────────────

    suspend fun createPlan(coupleId: String, plan: DatePlan): String {
        val doc = plansRef(coupleId).document()
        doc.set(plan.toEncryptedData(coupleId, includeCreatedAt = true), SetOptions.merge()).voidAwait()
        return doc.id
    }

    suspend fun updatePlan(coupleId: String, plan: DatePlan) {
        val path = plansRef(coupleId).document(plan.id)
        path.set(plan.toEncryptedData(coupleId, includeCreatedAt = false), SetOptions.merge()).voidAwait()
    }

    /**
     * Builds the Firestore payload with user content encrypted. dateIdeaId / scheduledDate /
     * status / timestamps stay plaintext because Firestore queries and ordering depend on them.
     */
    private fun DatePlan.toEncryptedData(coupleId: String, includeCreatedAt: Boolean): Map<String, Any?> {
        val aead = encryptionManager.requireAead(coupleId)
        val data = mutableMapOf<String, Any?>(
            "dateIdeaId" to dateIdeaId,
            "scheduledDate" to scheduledDate,
            "scheduledTime" to fieldEncryptor.encrypt(scheduledTime, aead, coupleId),
            "budget" to fieldEncryptor.encrypt(budget.toString(), aead, coupleId),
            "duration" to fieldEncryptor.encrypt(duration, aead, coupleId),
            "status" to status.toFirestoreValue(),
            "activity" to fieldEncryptor.encrypt(activity, aead, coupleId),
            "food" to fieldEncryptor.encrypt(food, aead, coupleId),
            "conversationPrompts" to fieldEncryptor.encrypt(JSONArray(conversationPrompts).toString(), aead, coupleId),
            "optionalChallenge" to fieldEncryptor.encryptNullable(optionalChallenge, aead, coupleId),
            "updatedAt" to updatedAt
        )
        if (includeCreatedAt) data["createdAt"] = createdAt
        return data
    }

    suspend fun getPlan(coupleId: String, planId: String): DatePlan? {
        val snap = plansRef(coupleId).document(planId).getDoc()
        return snap.toDatePlan(coupleId)
    }

    fun observePlans(coupleId: String): Flow<List<DatePlan>> {
        return callbackFlow {
            val listener = plansRef(coupleId)
                .orderBy("scheduledDate", com.google.firebase.firestore.Query.Direction.ASCENDING)
                .addSnapshotListener { snap, err ->
                    if (err != null || snap == null) return@addSnapshotListener
                    trySend(snap.documents.mapNotNull { it.toDatePlan(coupleId) })
                }
            awaitClose { listener.remove() }
        }
    }

    suspend fun getPlanByDateIdea(coupleId: String, dateIdeaId: String): DatePlan? {
        val snap = plansRef(coupleId)
            .whereEqualTo("dateIdeaId", dateIdeaId)
            .limit(1)
            .queryAwait()
        return snap.documents.firstOrNull()?.toDatePlan(coupleId)
    }

    suspend fun deletePlan(coupleId: String, planId: String) {
        plansRef(coupleId).document(planId).delete().voidAwait()
    }

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

    private suspend fun com.google.firebase.firestore.Query.queryAwait() =
        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) }
        }

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

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

    private fun com.google.firebase.firestore.DocumentSnapshot.toDatePlanPreference(coupleId: String): DatePlanPreference? {
        val dateIdeaId = getString("dateIdeaId") ?: return null
        val aead = encryptionManager.aeadFor(coupleId)
        return DatePlanPreference(
            id = id,
            coupleId = coupleId,
            dateIdeaId = dateIdeaId,
            preferredDate = (get("preferredDate") as? Number)?.toLong() ?: 0L,
            preferredTime = fieldEncryptor.decryptForDisplay(getString("preferredTime"), aead, coupleId) ?: "",
            budget = fieldEncryptor.decrypt(getString("budget"), aead, coupleId)?.toIntOrNull() ?: 0,
            duration = fieldEncryptor.decryptForDisplay(getString("duration"), aead, coupleId) ?: "",
            createdAt = (get("createdAt") as? Number)?.toLong() ?: 0L,
            updatedAt = (get("updatedAt") as? Number)?.toLong() ?: 0L
        )
    }

    private fun com.google.firebase.firestore.DocumentSnapshot.toDatePlan(coupleId: String): DatePlan? {
        val dateIdeaId = getString("dateIdeaId") ?: return null
        val scheduledDate = (get("scheduledDate") as? Number)?.toLong() ?: 0L
        val aead = encryptionManager.aeadFor(coupleId)
        val prompts = runCatching {
            val json = fieldEncryptor.decrypt(getString("conversationPrompts"), aead, coupleId)
            if (json != null) {
                val arr = JSONArray(json)
                (0 until arr.length()).map { arr.getString(it) }
            } else emptyList()
        }.getOrDefault(emptyList())
        return DatePlan(
            id = id,
            coupleId = coupleId,
            dateIdeaId = dateIdeaId,
            scheduledDate = scheduledDate,
            scheduledTime = fieldEncryptor.decryptForDisplay(getString("scheduledTime"), aead, coupleId) ?: "",
            budget = fieldEncryptor.decrypt(getString("budget"), aead, coupleId)?.toIntOrNull() ?: 0,
            duration = fieldEncryptor.decryptForDisplay(getString("duration"), aead, coupleId) ?: "",
            status = DatePlanStatus.fromFirestoreValue(getString("status") ?: "draft"),
            activity = fieldEncryptor.decryptForDisplay(getString("activity"), aead, coupleId) ?: "",
            food = fieldEncryptor.decryptForDisplay(getString("food"), aead, coupleId) ?: "",
            conversationPrompts = prompts,
            optionalChallenge = fieldEncryptor.decryptForDisplay(getString("optionalChallenge"), aead, coupleId),
            createdAt = (get("createdAt") as? Number)?.toLong() ?: 0L,
            updatedAt = (get("updatedAt") as? Number)?.toLong() ?: 0L
        )
    }
}
