package app.closer.crypto

import com.google.crypto.tink.aead.AeadConfig
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.BeforeClass
import org.junit.Test
import java.security.GeneralSecurityException

class SealedAnswerEncryptorTest {

    companion object {
        @BeforeClass
        @JvmStatic
        fun setup() {
            AeadConfig.register()
        }
    }

    private val subject = SealedAnswerEncryptor()

    private val coupleId = "couple-abc"
    private val questionId = "q-001"
    private val userId = "user-alice"

    private val payload = SealedAnswerEncryptor.AnswerPayload(
        writtenText = "I love rainy evenings at home.",
        selectedOptionIds = listOf("opt-b", "opt-a"),
        scaleValue = 7
    )

    @Test
    fun `sealed payload starts with sealed prefix`() {
        val key = subject.generateOneTimeKey()
        val sealed = subject.seal(payload, key, coupleId, questionId, userId)
        assertTrue(sealed.startsWith(SealedAnswerEncryptor.SEALED_PREFIX))
    }

    @Test
    fun `sealed payload does not contain plaintext`() {
        val key = subject.generateOneTimeKey()
        val sealed = subject.seal(payload, key, coupleId, questionId, userId)
        assertFalse(sealed.contains(payload.writtenText!!))
        payload.selectedOptionIds.forEach { assertFalse(sealed.contains(it)) }
    }

    @Test
    fun `round-trip restores all payload fields`() {
        val key = subject.generateOneTimeKey()
        val sealed = subject.seal(payload, key, coupleId, questionId, userId)
        val opened = subject.open(sealed, key, coupleId, questionId, userId)
        assertEquals(payload.writtenText, opened.writtenText)
        assertEquals(payload.selectedOptionIds.sorted(), opened.selectedOptionIds.sorted())
        assertEquals(payload.scaleValue, opened.scaleValue)
    }

    @Test
    fun `round-trip works for null fields`() {
        val key = subject.generateOneTimeKey()
        val nullPayload = SealedAnswerEncryptor.AnswerPayload(null, emptyList(), null)
        val sealed = subject.seal(nullPayload, key, coupleId, questionId, userId)
        val opened = subject.open(sealed, key, coupleId, questionId, userId)
        assertNull(opened.writtenText)
        assertEquals(emptyList<String>(), opened.selectedOptionIds)
        assertNull(opened.scaleValue)
    }

    @Test(expected = GeneralSecurityException::class)
    fun `wrong key cannot decrypt`() {
        val aliceKey = subject.generateOneTimeKey()
        val bobKey = subject.generateOneTimeKey()
        val sealed = subject.seal(payload, aliceKey, coupleId, questionId, userId)
        subject.open(sealed, bobKey, coupleId, questionId, userId)
    }

    @Test(expected = GeneralSecurityException::class)
    fun `wrong coupleId cannot decrypt`() {
        val key = subject.generateOneTimeKey()
        val sealed = subject.seal(payload, key, coupleId, questionId, userId)
        subject.open(sealed, key, "other-couple", questionId, userId)
    }

    @Test(expected = GeneralSecurityException::class)
    fun `wrong questionId cannot decrypt`() {
        val key = subject.generateOneTimeKey()
        val sealed = subject.seal(payload, key, coupleId, questionId, userId)
        subject.open(sealed, key, coupleId, "other-question", userId)
    }

    @Test(expected = GeneralSecurityException::class)
    fun `wrong userId cannot decrypt`() {
        val key = subject.generateOneTimeKey()
        val sealed = subject.seal(payload, key, coupleId, questionId, userId)
        subject.open(sealed, key, coupleId, questionId, "other-user")
    }

    @Test
    fun `key serialization round-trip`() {
        val key = subject.generateOneTimeKey()
        val json = subject.serializeKey(key)
        val restored = subject.deserializeKey(json)
        val sealed = subject.seal(payload, key, coupleId, questionId, userId)
        val opened = subject.open(sealed, restored, coupleId, questionId, userId)
        assertEquals(payload.writtenText, opened.writtenText)
    }

    @Test
    fun `each seal call produces different ciphertext`() {
        val key = subject.generateOneTimeKey()
        val s1 = subject.seal(payload, key, coupleId, questionId, userId)
        val s2 = subject.seal(payload, key, coupleId, questionId, userId)
        // AES-256-GCM with random nonce: same plaintext → different ciphertext.
        assertFalse(s1 == s2)
    }

    @Test
    fun `special characters in answer survive round-trip`() {
        val key = subject.generateOneTimeKey()
        val special = SealedAnswerEncryptor.AnswerPayload(
            writtenText = "she said \"hello\"\nand left.\ttab here",
            selectedOptionIds = emptyList(),
            scaleValue = null
        )
        val sealed = subject.seal(special, key, coupleId, questionId, userId)
        val opened = subject.open(sealed, key, coupleId, questionId, userId)
        assertEquals(special.writtenText, opened.writtenText)
    }
}
