package app.closer.data.remote

import android.util.Log
import app.closer.crypto.CoupleEncryptionManager
import app.closer.crypto.FieldEncryptor
import app.closer.domain.model.TimeCapsule
import com.google.firebase.firestore.FirebaseFirestore
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
import javax.inject.Singleton

private const val TAG = "FirestoreCapsuleDS"

@Singleton
class FirestoreCapsuleDataSource @Inject constructor(
    private val db: FirebaseFirestore,
    private val encryptionManager: CoupleEncryptionManager,
    private val fieldEncryptor: FieldEncryptor
) {

    private fun col(coupleId: String) =
        db.collection(FirestoreCollections.COUPLES)
            .document(coupleId)
            .collection(FirestoreCollections.Couples.CAPSULES)

    /**
     * Decrypts a field value.
     * - Plaintext (unencrypted legacy value): returned as-is for backward compat.
     * - Encrypted but key unavailable: returns null so the UI can show a placeholder.
     * - Decryption failure: returns null rather than leaking a garbled ciphertext.
     */
    private fun decryptField(value: String?, coupleId: String): String? {
        if (value == null) return null
        if (!fieldEncryptor.isEncrypted(value)) return value  // legacy plaintext
        val aead = encryptionManager.aeadFor(coupleId) ?: run {
            Log.w(TAG, "key unavailable for $coupleId, masking encrypted field")
            return null
        }
        return runCatching { fieldEncryptor.decrypt(value, aead, coupleId) }
            .getOrElse { e ->
                Log.w(TAG, "decrypt failed: ${e.message}")
                null
            }
    }

    private fun mapToCapsule(doc: com.google.firebase.firestore.DocumentSnapshot, coupleId: String): TimeCapsule {
        val rawTitle = doc.getString("title") ?: ""
        val rawContent = doc.getString("content") ?: ""
        val rawPrompt = doc.getString("promptUsed")
        return TimeCapsule(
            id = doc.id,
            coupleId = coupleId,
            authorId = doc.getString("authorId") ?: "",
            title = decryptField(rawTitle, coupleId) ?: app.closer.crypto.FieldEncryptor.LOCKED_PLACEHOLDER,
            content = decryptField(rawContent, coupleId) ?: app.closer.crypto.FieldEncryptor.LOCKED_PLACEHOLDER,
            promptUsed = decryptField(rawPrompt, coupleId),
            unlockAt = doc.getLong("unlockAt") ?: 0L,
            createdAt = doc.getLong("createdAt") ?: 0L,
            status = doc.getString("status") ?: "sealed"
        )
    }

    fun observeCapsules(coupleId: String): Flow<List<TimeCapsule>> = callbackFlow {
        val reg = col(coupleId)
            .orderBy("createdAt", com.google.firebase.firestore.Query.Direction.DESCENDING)
            .addSnapshotListener { snap, err ->
                // Propagate listener failures (e.g. PERMISSION_DENIED) by closing the flow so
                // the collector's error handling runs. Previously this swallowed the error
                // (`return@`), so the flow never emitted or closed and the Memory Lane screen
                // hung on its loading indicator forever (F-OBS).
                if (err != null) { close(err); return@addSnapshotListener }
                if (snap == null) return@addSnapshotListener
                trySend(snap.documents.mapNotNull { doc ->
                    runCatching { mapToCapsule(doc, coupleId) }.getOrNull()
                })
            }
        awaitClose { reg.remove() }
    }

    /**
     * Writes a capsule. Requires the couple encryption key to be available — throws
     * [IllegalStateException] if the key is missing so the caller can surface an error
     * rather than silently storing plaintext.
     */
    suspend fun createCapsule(capsule: TimeCapsule): String {
        val aead = encryptionManager.aeadFor(capsule.coupleId)
            ?: throw IllegalStateException("Encryption key not ready. Please try again in a moment.")
        val encTitle = fieldEncryptor.encrypt(capsule.title, aead, capsule.coupleId)
        val encContent = fieldEncryptor.encrypt(capsule.content, aead, capsule.coupleId)
        val encPrompt = fieldEncryptor.encryptNullable(capsule.promptUsed, aead, capsule.coupleId)

        val ref = col(capsule.coupleId).document()
        ref.set(
            mapOf(
                "authorId" to capsule.authorId,
                "title" to encTitle,
                "content" to encContent,
                "promptUsed" to encPrompt,
                "unlockAt" to capsule.unlockAt,
                "createdAt" to capsule.createdAt,
                "status" to "sealed"
            )
        ).await()
        return ref.id
    }

    suspend fun getCapsules(coupleId: String): List<TimeCapsule> =
        col(coupleId)
            .orderBy("createdAt", com.google.firebase.firestore.Query.Direction.DESCENDING)
            .get()
            .await()
            .documents
            .mapNotNull { doc ->
                runCatching { mapToCapsule(doc, coupleId) }.getOrNull()
            }

    suspend fun unlockCapsule(coupleId: String, capsuleId: String) {
        col(coupleId).document(capsuleId).update("status", "unlocked").await()
    }

    suspend fun updateCapsule(capsule: TimeCapsule) {
        val aead = encryptionManager.aeadFor(capsule.coupleId)
            ?: throw IllegalStateException("Encryption key not ready. Please try again in a moment.")
        val encTitle = fieldEncryptor.encrypt(capsule.title, aead, capsule.coupleId)
        val encContent = fieldEncryptor.encrypt(capsule.content, aead, capsule.coupleId)
        val encPrompt = fieldEncryptor.encryptNullable(capsule.promptUsed, aead, capsule.coupleId)
        col(capsule.coupleId).document(capsule.id).update(
            mapOf(
                "title" to encTitle,
                "content" to encContent,
                "promptUsed" to encPrompt,
                "unlockAt" to capsule.unlockAt
            )
        ).await()
    }

    suspend fun deleteCapsule(coupleId: String, capsuleId: String) {
        col(coupleId).document(capsuleId).delete().await()
    }
}
