package app.closer.data.remote

import app.closer.crypto.RecoveryKeyManager
import app.closer.domain.model.Invite
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.Timestamp
import com.google.firebase.functions.FirebaseFunctions
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
import kotlin.random.Random

@Singleton
class FirestoreInviteDataSource @Inject constructor(
    private val db: FirebaseFirestore,
    private val functions: FirebaseFunctions
) {
    private fun inviteRef(code: String) = db.collection(FirestoreCollections.INVITES).document(code)

    fun generateCode(): String = (1..6)
        .map { CODE_CHARS[Random.nextInt(CODE_CHARS.length)] }
        .joinToString("")

    suspend fun createInvite(
        code: String,
        inviterUserId: String,
        wrappedKey: RecoveryKeyManager.WrappedKey
    ): Unit = suspendCancellableCoroutine { cont ->
        val now = System.currentTimeMillis()
        inviteRef(code).set(
            mapOf(
                "code" to code,
                "inviterUserId" to inviterUserId,
                "status" to "pending",
                "createdAt" to Timestamp.now(),
                "expiresAt" to Timestamp(now / 1000 + 24 * 60 * 60, 0),
                "wrappedCoupleKey" to wrappedKey.cipherB64,
                "kdfSalt" to wrappedKey.saltB64,
                "kdfParams" to wrappedKey.params
            )
        )
            .addOnSuccessListener { cont.resume(Unit) }
            .addOnFailureListener { cont.resumeWithException(it) }
    }

    suspend fun getInviteByCode(code: String): Invite? =
        suspendCancellableCoroutine { cont ->
            inviteRef(code).get()
                .addOnSuccessListener { snap ->
                    if (!snap.exists()) { cont.resume(null); return@addOnSuccessListener }
                    cont.resume(
                        Invite(
                            id = snap.id,
                            code = snap.getString("code") ?: snap.id,
                            inviterUserId = snap.getString("inviterUserId") ?: "",
                            inviteeEmail = snap.getString("inviteeEmail"),
                            coupleId = snap.getString("coupleId"),
                            status = snap.getString("status") ?: "pending",
                            createdAt = snap.getTimestamp("createdAt")?.toDate()?.time
                                ?: snap.getLong("createdAt") ?: 0L,
                            expiresAt = snap.getTimestamp("expiresAt")?.toDate()?.time
                                ?: snap.getLong("expiresAt") ?: 0L,
                            acceptedAt = snap.getTimestamp("acceptedAt")?.toDate()?.time
                                ?: snap.getLong("acceptedAt"),
                            acceptedByUserId = snap.getString("acceptedByUserId"),
                            wrappedCoupleKey = snap.getString("wrappedCoupleKey"),
                            kdfSalt = snap.getString("kdfSalt"),
                            kdfParams = snap.getString("kdfParams")
                        )
                    )
                }
                .addOnFailureListener { cont.resumeWithException(it) }
        }

    /**
     * Accepts an invite server-side via the [acceptInviteCallable] Cloud Function.
     *
     * The client no longer reads the invite document directly (issue #9 fix).
     * Instead, the function validates the code, creates the couple, updates both
     * user documents, and returns the inviter UID and wrapped key so the acceptor
     * can decrypt the couple keyset locally.
     */
    suspend fun acceptInvite(code: String, recoveryPhrase: String): app.closer.domain.repository.AcceptInviteResult {
        @Suppress("DEPRECATION")
        val result = functions.getHttpsCallable("acceptInviteCallable")
            .call(mapOf("code" to code, "recoveryPhrase" to recoveryPhrase))
            .await()
        val data = result.getData() as? Map<*, *>
            ?: throw IllegalStateException("Invalid response from acceptInviteCallable")

        val coupleId = data["coupleId"] as? String
            ?: throw IllegalStateException("Missing coupleId in acceptInvite response")
        val inviterUserId = data["inviterUserId"] as? String
            ?: throw IllegalStateException("Missing inviterUserId in acceptInvite response")
        val wrappedCoupleKey = data["wrappedCoupleKey"] as? String
            ?: throw IllegalStateException("Missing wrappedCoupleKey in acceptInvite response")
        val kdfSalt = data["kdfSalt"] as? String
            ?: throw IllegalStateException("Missing kdfSalt in acceptInvite response")
        val kdfParams = data["kdfParams"] as? String
            ?: throw IllegalStateException("Missing kdfParams in acceptInvite response")

        return app.closer.domain.repository.AcceptInviteResult(
            coupleId = coupleId,
            inviterUserId = inviterUserId,
            wrappedKey = RecoveryKeyManager.WrappedKey(
                cipherB64 = wrappedCoupleKey,
                saltB64 = kdfSalt,
                params = kdfParams
            )
        )
    }

    companion object {
        private const val CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
    }
}
