package app.closer.data.remote

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 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 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) {
        val path = preferencesRef(coupleId).document()
        val data = mapOf(
            "dateIdeaId" to preference.dateIdeaId,
            "preferredDate" to preference.preferredDate,
            "preferredTime" to preference.preferredTime,
            "budget" to preference.budget,
            "duration" to preference.duration,
            "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()
        val data = mapOf(
            "dateIdeaId" to plan.dateIdeaId,
            "scheduledDate" to plan.scheduledDate,
            "scheduledTime" to plan.scheduledTime,
            "budget" to plan.budget,
            "duration" to plan.duration,
            "status" to plan.status.toFirestoreValue(),
            "activity" to plan.activity,
            "food" to plan.food,
            "conversationPrompts" to plan.conversationPrompts,
            "optionalChallenge" to plan.optionalChallenge,
            "createdAt" to plan.createdAt,
            "updatedAt" to plan.updatedAt
        )
        doc.set(data, SetOptions.merge()).voidAwait()
        return doc.id
    }

    suspend fun updatePlan(coupleId: String, plan: DatePlan) {
        val path = plansRef(coupleId).document(plan.id)
        val data = mapOf(
            "dateIdeaId" to plan.dateIdeaId,
            "scheduledDate" to plan.scheduledDate,
            "scheduledTime" to plan.scheduledTime,
            "budget" to plan.budget,
            "duration" to plan.duration,
            "status" to plan.status.toFirestoreValue(),
            "activity" to plan.activity,
            "food" to plan.food,
            "conversationPrompts" to plan.conversationPrompts,
            "optionalChallenge" to plan.optionalChallenge,
            "updatedAt" to plan.updatedAt
        )
        path.set(data, SetOptions.merge()).voidAwait()
    }

    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 ─────────────────────────────────────────────────────────────

    @Suppress("UNCHECKED_CAST")
    private fun com.google.firebase.firestore.DocumentSnapshot.toDatePlanPreference(coupleId: String): DatePlanPreference? {
        val dateIdeaId = getString("dateIdeaId") ?: return null
        return DatePlanPreference(
            id = id,
            coupleId = coupleId,
            dateIdeaId = dateIdeaId,
            preferredDate = (get("preferredDate") as? Number)?.toLong() ?: 0L,
            preferredTime = getString("preferredTime") ?: "",
            budget = (get("budget") as? Number)?.toInt() ?: 0,
            duration = getString("duration") ?: "",
            createdAt = (get("createdAt") as? Number)?.toLong() ?: 0L,
            updatedAt = (get("updatedAt") as? Number)?.toLong() ?: 0L
        )
    }

    @Suppress("UNCHECKED_CAST")
    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
        return DatePlan(
            id = id,
            coupleId = coupleId,
            dateIdeaId = dateIdeaId,
            scheduledDate = scheduledDate,
            scheduledTime = getString("scheduledTime") ?: "",
            budget = (get("budget") as? Number)?.toInt() ?: 0,
            duration = getString("duration") ?: "",
            status = DatePlanStatus.fromFirestoreValue(getString("status") ?: "draft"),
            activity = getString("activity") ?: "",
            food = getString("food") ?: "",
            conversationPrompts = (get("conversationPrompts") as? List<String>) ?: emptyList(),
            optionalChallenge = getString("optionalChallenge"),
            createdAt = (get("createdAt") as? Number)?.toLong() ?: 0L,
            updatedAt = (get("updatedAt") as? Number)?.toLong() ?: 0L
        )
    }
}
