package app.closer.data.remote

import app.closer.crypto.RecoveryKeyManager
import app.closer.domain.repository.AcceptInviteResult
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.functions.FirebaseFunctions
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

@Singleton
class FirestoreInviteDataSource @Inject constructor(
    private val db: FirebaseFirestore,
    private val functions: FirebaseFunctions
) {
    /**
     * Creates an invite server-side. [code] is client-generated and used as both the
     * Firestore document ID and the KDF input for phrase encryption; the server validates
     * uniqueness and returns the confirmed code. [encryptedRecoveryPhrase] is an
     * Argon2id+AES-GCM blob produced by [RecoveryKeyManager.encryptPhraseWithCode] —
     * the server stores it opaquely and never sees the plaintext phrase.
     */
    suspend fun createInvite(
        code: String,
        wrappedKey: RecoveryKeyManager.WrappedKey,
        encryptedRecoveryPhrase: String
    ): CreateInviteResponse {
        @Suppress("DEPRECATION")
        val result = functions.getHttpsCallable("createInviteCallable")
            .call(
                mapOf(
                    "code" to code,
                    "wrappedCoupleKey" to wrappedKey.cipherB64,
                    "kdfSalt" to wrappedKey.saltB64,
                    "kdfParams" to wrappedKey.params,
                    "encryptedRecoveryPhrase" to encryptedRecoveryPhrase
                )
            )
            .await()
        val data = result.getData() as? Map<*, *>
            ?: throw IllegalStateException("Invalid response from createInviteCallable")
        val returnedCode = data["code"] as? String
            ?: throw IllegalStateException("Missing code in createInviteCallable response")
        // Callable SDK returns Timestamps as Map<_seconds, _nanoseconds>, not firebase.Timestamp.
        val expiresAt: com.google.firebase.Timestamp? = when (val raw = data["expiresAt"]) {
            is com.google.firebase.Timestamp -> raw
            is Map<*, *> -> {
                val s = (raw["_seconds"] as? Long) ?: (raw["_seconds"] as? Int)?.toLong()
                val n = (raw["_nanoseconds"] as? Long) ?: (raw["_nanoseconds"] as? Int)?.toLong()
                if (s != null && n != null) com.google.firebase.Timestamp(s, n.toInt()) else null
            }
            else -> null
        }
        return CreateInviteResponse(returnedCode, expiresAt)
    }

    fun watchInviteAccepted(code: String): Flow<String?> = callbackFlow {
        val listener = db.collection("invites").document(code)
            .addSnapshotListener { snapshot, _ ->
                if (snapshot?.getString("status") == "accepted") {
                    trySend(snapshot.getString("coupleId"))
                }
            }
        awaitClose { listener.remove() }
    }

    data class CreateInviteResponse(
        val code: String,
        val expiresAt: com.google.firebase.Timestamp? = null
    )

    /**
     * Accepts an invite server-side. Returns [AcceptInviteResult] with
     * [AcceptInviteResult.recoveryPhrase] holding the raw encrypted blob from the server;
     * the caller ([InviteRepositoryImpl]) decrypts it with the invite code before
     * surfacing it to the domain layer.
     */
    suspend fun acceptInvite(code: String): AcceptInviteResult {
        @Suppress("DEPRECATION")
        val result = functions.getHttpsCallable("acceptInviteCallable")
            .call(mapOf("code" to code))
            .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")
        // Raw encrypted blob — repository decrypts before returning to callers.
        val encryptedRecoveryPhrase = data["encryptedRecoveryPhrase"] as? String

        return AcceptInviteResult(
            coupleId = coupleId,
            inviterUserId = inviterUserId,
            wrappedKey = RecoveryKeyManager.WrappedKey(
                cipherB64 = wrappedCoupleKey,
                saltB64 = kdfSalt,
                params = kdfParams
            ),
            recoveryPhrase = encryptedRecoveryPhrase
        )
    }
}
