package app.closer.crypto

import com.google.crypto.tink.Aead
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import java.security.GeneralSecurityException
import java.security.MessageDigest

class FieldEncryptorTest {
    private val subject = FieldEncryptor()
    private val aead = AssociatedDataCheckingAead()

    @Test
    fun `encrypted wire value never contains the plaintext`() {
        val encrypted = subject.encrypt("our private answer", aead, "couple-a")

        assertTrue(encrypted.startsWith(FieldEncryptor.PREFIX))
        assertFalse(encrypted.contains("our private answer"))
        assertTrue(subject.isEncrypted(encrypted))
    }

    @Test
    fun `encrypted value round trips for the same couple`() {
        val encrypted = subject.encrypt("yes, absolutely", aead, "couple-a")

        assertEquals("yes, absolutely", subject.decrypt(encrypted, aead, "couple-a"))
    }

    @Test
    fun `ciphertext cannot be moved to a different couple`() {
        val encrypted = subject.encrypt("bound to us", aead, "couple-a")

        assertNull(subject.decrypt(encrypted, aead, "couple-b"))
    }

    @Test
    fun `encrypted value stays unreadable when the key is unavailable`() {
        val encrypted = subject.encrypt("key required", aead, "couple-a")

        assertNull(subject.decrypt(encrypted, null, "couple-a"))
    }

    @Test
    fun `unprefixed value is not trusted as plaintext (fail closed)`() {
        // No legacy plaintext exists; an unprefixed value must never pass through.
        assertNull(subject.decrypt("sneaky plaintext", aead, "couple-a"))
    }

    @Test
    fun `decryptForDisplay shows the locked placeholder for unreadable content`() {
        // Encrypted but no key on this device.
        val encrypted = subject.encrypt("private", aead, "couple-a")
        assertEquals(FieldEncryptor.LOCKED_PLACEHOLDER, subject.decryptForDisplay(encrypted, null, "couple-a"))

        // Encrypted for a different couple (wrong AAD).
        assertEquals(FieldEncryptor.LOCKED_PLACEHOLDER, subject.decryptForDisplay(encrypted, aead, "couple-b"))

        // Unprefixed value is treated as locked, never shown as plaintext.
        assertEquals(FieldEncryptor.LOCKED_PLACEHOLDER, subject.decryptForDisplay("sneaky plaintext", aead, "couple-a"))
    }

    @Test
    fun `decryptForDisplay round trips a valid encrypted value`() {
        val encrypted = subject.encrypt("hello", aead, "couple-a")
        assertEquals("hello", subject.decryptForDisplay(encrypted, aead, "couple-a"))
    }

    @Test
    fun `decryptForDisplay passes through null`() {
        assertNull(subject.decryptForDisplay(null, aead, "couple-a"))
    }

    private class AssociatedDataCheckingAead : Aead {
        override fun encrypt(plaintext: ByteArray, associatedData: ByteArray): ByteArray =
            digest(associatedData) + plaintext.reversedArray()

        override fun decrypt(ciphertext: ByteArray, associatedData: ByteArray): ByteArray {
            val expected = digest(associatedData)
            if (ciphertext.size < expected.size || !ciphertext.copyOfRange(0, expected.size).contentEquals(expected)) {
                throw GeneralSecurityException("Associated data does not match")
            }
            return ciphertext.copyOfRange(expected.size, ciphertext.size).reversedArray()
        }

        private fun digest(value: ByteArray): ByteArray =
            MessageDigest.getInstance("SHA-256").digest(value).copyOf(8)
    }
}
