package app.closer.data.remote

import app.closer.crypto.CoupleEncryptionManager
import app.closer.crypto.FieldEncryptor
import com.google.crypto.tink.Aead
import com.google.firebase.firestore.DocumentReference
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.FieldPath
import com.google.firebase.firestore.FirebaseFirestore
import kotlinx.coroutines.tasks.await
import org.json.JSONArray
import org.json.JSONObject
import javax.inject.Inject
import javax.inject.Singleton

/** One-time, per-user rewrite of every historical answer-bearing field to ciphertext. */
@Singleton
class CoupleAnswerMigrationDataSource @Inject constructor(
    private val db: FirebaseFirestore,
    private val encryptionManager: CoupleEncryptionManager,
    private val fieldEncryptor: FieldEncryptor
) {
    suspend fun migrateUser(coupleId: String, userId: String) {
        val aead = encryptionManager.requireAead(coupleId)
        migrateDailyAnswers(coupleId, userId, aead)
        migrateThreadContent(coupleId, userId, aead)
        migrateThisOrThat(coupleId, userId, aead)
        migrateDesireSync(coupleId, userId, aead)
        migrateHowWell(coupleId, userId, aead)
        migrateWheel(coupleId, userId, aead)
    }

    private fun coupleRef(coupleId: String) =
        db.collection(FirestoreCollections.COUPLES).document(coupleId)

    private suspend fun migrateDailyAnswers(coupleId: String, userId: String, aead: Aead) {
        val days = coupleRef(coupleId)
            .collection(FirestoreCollections.Couples.DAILY_QUESTION)
            .get().await()
        for (day in days.documents) {
            val ref = day.reference
                .collection(FirestoreCollections.DailyQuestion.ANSWERS)
                .document(userId)
            migrateAnswerDocument(ref, coupleId, aead)
        }
    }

    private suspend fun migrateThreadContent(coupleId: String, userId: String, aead: Aead) {
        val threads = coupleRef(coupleId)
            .collection(FirestoreCollections.Couples.QUESTION_THREADS)
            .get().await()
        for (thread in threads.documents) {
            migrateAnswerDocument(
                thread.reference.collection(FirestoreCollections.QuestionThreads.ANSWERS).document(userId),
                coupleId,
                aead
            )
            val messages = thread.reference
                .collection(FirestoreCollections.QuestionThreads.MESSAGES)
                .whereEqualTo("authorUserId", userId)
                .get().await()
            for (message in messages.documents) {
                val text = message.getString("text") ?: continue
                if (!fieldEncryptor.isEncrypted(text)) {
                    message.reference.update(
                        "text",
                        fieldEncryptor.encrypt(text, aead, coupleId)
                    ).await()
                }
            }
        }
    }

    private suspend fun migrateAnswerDocument(ref: DocumentReference, coupleId: String, aead: Aead) {
        val snapshot = ref.get().await()
        if (!snapshot.exists()) return
        val updates = encryptedAnswerUpdates(snapshot, coupleId, aead)
        if (updates.isNotEmpty()) ref.update(updates).await()
    }

    private fun encryptedAnswerUpdates(
        snapshot: DocumentSnapshot,
        coupleId: String,
        aead: Aead
    ): Map<String, Any> {
        val updates = mutableMapOf<String, Any>()
        snapshot.getString("writtenText")?.let { value ->
            if (!fieldEncryptor.isEncrypted(value)) {
                updates["writtenText"] = fieldEncryptor.encrypt(value, aead, coupleId)
            }
        }

        val rawIds = (snapshot.get("selectedOptionIds") as? List<*>)?.filterIsInstance<String>()
        if (!rawIds.isNullOrEmpty() && !(rawIds.size == 1 && fieldEncryptor.isEncrypted(rawIds[0]))) {
            updates["selectedOptionIds"] = listOf(
                fieldEncryptor.encrypt(JSONArray(rawIds).toString(), aead, coupleId)
            )
        }

        snapshot.get("scaleValue")?.let { value ->
            if (value !is String || !fieldEncryptor.isEncrypted(value)) {
                updates["scaleValue"] = fieldEncryptor.encrypt(value.toString(), aead, coupleId)
            }
        }
        return updates
    }

    private suspend fun migrateThisOrThat(coupleId: String, userId: String, aead: Aead) {
        migrateUserAnswerCollection(
            coupleId,
            userId,
            FirestoreCollections.Couples.THIS_OR_THAT
        ) { value ->
            val answers = (value as? List<*>)?.filterIsInstance<String>() ?: return@migrateUserAnswerCollection null
            fieldEncryptor.encrypt(JSONArray(answers).toString(), aead, coupleId)
        }
    }

    private suspend fun migrateDesireSync(coupleId: String, userId: String, aead: Aead) {
        migrateUserAnswerCollection(
            coupleId,
            userId,
            FirestoreCollections.Couples.DESIRE_SYNC
        ) { value ->
            val list = (value as? List<*>)?.filterIsInstance<String>() ?: return@migrateUserAnswerCollection null
            if (list.size == 1 && fieldEncryptor.isEncrypted(list[0])) list[0]
            else fieldEncryptor.encrypt(JSONArray(list).toString(), aead, coupleId)
        }
    }

    private suspend fun migrateHowWell(coupleId: String, userId: String, aead: Aead) {
        migrateUserAnswerCollection(
            coupleId,
            userId,
            FirestoreCollections.Couples.HOW_WELL
        ) { value ->
            val list = value as? List<*> ?: return@migrateUserAnswerCollection null
            if (list.size == 1 && list[0] is String && fieldEncryptor.isEncrypted(list[0] as String)) {
                list[0] as String
            } else {
                fieldEncryptor.encrypt(JSONArray(list).toString(), aead, coupleId)
            }
        }
    }

    private suspend fun migrateWheel(coupleId: String, userId: String, aead: Aead) {
        migrateUserAnswerCollection(
            coupleId,
            userId,
            FirestoreCollections.Couples.WHEEL
        ) { value ->
            val list = value as? List<*> ?: return@migrateUserAnswerCollection null
            val normalized = list.mapNotNull { item ->
                val map = item as? Map<*, *> ?: return@mapNotNull null
                val rawDisplay = map["display"] as? String ?: ""
                val display = fieldEncryptor.decrypt(rawDisplay, aead, coupleId) ?: rawDisplay
                JSONObject().apply {
                    put("questionId", map["questionId"] as? String ?: "")
                    put("display", display)
                }
            }
            fieldEncryptor.encrypt(JSONArray(normalized).toString(), aead, coupleId)
        }
    }

    private suspend fun migrateUserAnswerCollection(
        coupleId: String,
        userId: String,
        collection: String,
        encryptLegacyValue: (Any?) -> String?
    ) {
        val documents = coupleRef(coupleId).collection(collection).get().await()
        for (document in documents.documents) {
            val value = (document.get("answers") as? Map<*, *>)?.get(userId) ?: continue
            if (value is String && fieldEncryptor.isEncrypted(value)) continue
            val encrypted = encryptLegacyValue(value) ?: continue
            document.reference.update(FieldPath.of("answers", userId), encrypted).await()
        }
    }
}
