package app.closer.crypto

import com.google.crypto.tink.Aead
import java.util.Base64
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Stateless helper that encrypts/decrypts individual Firestore field values.
 *
 * Wire format: "enc:v1:{base64(tinkCiphertext)}"
 *
 * Fail-closed: every couple is strict-E2EE, so all stored content is encrypted.
 * A value WITHOUT the prefix is not treated as trusted plaintext — [decrypt]
 * returns null for it. There is no plaintext fallback. Writers must always go
 * through [encrypt] (callers use requireAead so a missing key throws rather than
 * writing plaintext).
 *
 * AAD = coupleId bytes -- binds ciphertext to the couple and prevents
 * copy-paste of one couple's ciphertext into another couple's document.
 */
@Singleton
class FieldEncryptor @Inject constructor() {

    fun encrypt(plaintext: String, aead: Aead, coupleId: String): String {
        val cipher = aead.encrypt(
            plaintext.toByteArray(Charsets.UTF_8),
            coupleId.toByteArray(Charsets.UTF_8)
        )
        return PREFIX + Base64.getEncoder().encodeToString(cipher)
    }

    fun encryptNullable(value: String?, aead: Aead, coupleId: String): String? =
        value?.let { encrypt(it, aead, coupleId) }

    /**
     * Decrypts if the value has the enc:v1: prefix; returns the original string otherwise.
     * Returns null when [value] is null or [aead] is null and the value is encrypted.
     */
    fun decrypt(value: String?, aead: Aead?, coupleId: String): String? {
        if (value == null) return null
        // Fail-closed: a value without the enc:v1: prefix is not trusted plaintext.
        // All content is encrypted, so an unprefixed value is unexpected and rejected.
        if (!value.startsWith(PREFIX)) return null
        if (aead == null) return null
        return runCatching {
            val cipher = Base64.getDecoder().decode(value.removePrefix(PREFIX))
            aead.decrypt(cipher, coupleId.toByteArray(Charsets.UTF_8))
                .toString(Charsets.UTF_8)
        }.getOrNull()
    }

    fun isEncrypted(value: String?): Boolean = value?.startsWith(PREFIX) == true

    /**
     * Display-safe decrypt. Returns:
     *  - the decrypted text for `enc:v1:` values,
     *  - the value unchanged for legacy plaintext,
     *  - [LOCKED_PLACEHOLDER] when the value IS encrypted but cannot be decrypted on
     *    this device (missing/wrong couple key) — never the raw ciphertext.
     *
     * Use this anywhere a decrypted value is shown to the user. [decrypt] (which returns
     * null on failure) is reserved for structured fields that are parsed, not displayed.
     */
    fun decryptForDisplay(value: String?, aead: Aead?, coupleId: String): String? {
        if (value == null) return null
        // decrypt() returns null only when the value is encrypted but unreadable here.
        return decrypt(value, aead, coupleId) ?: LOCKED_PLACEHOLDER
    }

    companion object {
        const val PREFIX = "enc:v1:"

        /** Shown in place of content that is encrypted but cannot be decrypted on this device. */
        const val LOCKED_PLACEHOLDER = "🔒 Couldn't unlock on this device"
    }
}
