package com.bytelyst.platform

import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.assertThrows
import javax.crypto.AEADBadTagException
import javax.crypto.SecretKey

class BLFieldEncryptTest {

    private lateinit var key: SecretKey
    private val dekId = "dek_user1_test"

    @BeforeEach
    fun setUp() {
        key = BLFieldEncrypt.generateKey()
    }

    // ── Encrypt / Decrypt Roundtrip ─────────────────────────

    @Test
    fun `encrypt decrypt roundtrip`() {
        val plaintext = "Hello, World!"
        val encrypted = BLFieldEncrypt.encrypt(plaintext, key, dekId)
        val decrypted = BLFieldEncrypt.decrypt(encrypted, key)
        assertEquals(plaintext, decrypted)
    }

    @Test
    fun `encrypt decrypt empty string`() {
        val plaintext = ""
        val encrypted = BLFieldEncrypt.encrypt(plaintext, key, dekId)
        val decrypted = BLFieldEncrypt.decrypt(encrypted, key)
        assertEquals(plaintext, decrypted)
    }

    @Test
    fun `encrypt decrypt unicode`() {
        val plaintext = "こんにちは世界 \uD83C\uDF0D مرحبا Ñoño"
        val encrypted = BLFieldEncrypt.encrypt(plaintext, key, dekId)
        val decrypted = BLFieldEncrypt.decrypt(encrypted, key)
        assertEquals(plaintext, decrypted)
    }

    @Test
    fun `encrypt decrypt large payload`() {
        val plaintext = "A".repeat(100_000)
        val encrypted = BLFieldEncrypt.encrypt(plaintext, key, dekId)
        val decrypted = BLFieldEncrypt.decrypt(encrypted, key)
        assertEquals(plaintext, decrypted)
    }

    // ── EncryptedField Structure ────────────────────────────

    @Test
    fun `encrypted field has correct sentinel`() {
        val encrypted = BLFieldEncrypt.encrypt("test", key, dekId)
        assertTrue(encrypted.__encrypted)
        assertEquals(1, encrypted.v)
        assertEquals("aes-256-gcm", encrypted.alg)
        assertEquals(dekId, encrypted.dekId)
    }

    @Test
    fun `encrypted field has correct hex lengths`() {
        val encrypted = BLFieldEncrypt.encrypt("test", key, dekId)
        // IV: 12 bytes = 24 hex chars
        assertEquals(24, encrypted.iv.length)
        // Tag: 16 bytes = 32 hex chars
        assertEquals(32, encrypted.tag.length)
        // ct should be non-empty
        assertTrue(encrypted.ct.isNotEmpty())
    }

    @Test
    fun `each encryption produces unique IV`() {
        val a = BLFieldEncrypt.encrypt("same", key, dekId)
        val b = BLFieldEncrypt.encrypt("same", key, dekId)
        assertNotEquals(a.iv, b.iv, "Each encryption should use a unique IV")
        assertNotEquals(a.ct, b.ct, "Ciphertext should differ with different IVs")
    }

    // ── AAD (Additional Authenticated Data) ─────────────────

    @Test
    fun `encrypt decrypt with AAD`() {
        val plaintext = "secret data"
        val aad = "user123:notes"
        val encrypted = BLFieldEncrypt.encrypt(plaintext, key, dekId, aad)
        val decrypted = BLFieldEncrypt.decrypt(encrypted, key, aad)
        assertEquals(plaintext, decrypted)
    }

    @Test
    fun `decrypt with wrong AAD fails`() {
        val encrypted = BLFieldEncrypt.encrypt("secret", key, dekId, "correct")
        assertThrows<AEADBadTagException> {
            BLFieldEncrypt.decrypt(encrypted, key, "wrong")
        }
    }

    @Test
    fun `decrypt with missing AAD fails`() {
        val encrypted = BLFieldEncrypt.encrypt("secret", key, dekId, "some-aad")
        assertThrows<AEADBadTagException> {
            BLFieldEncrypt.decrypt(encrypted, key)
        }
    }

    // ── Wrong Key ───────────────────────────────────────────

    @Test
    fun `decrypt with wrong key fails`() {
        val encrypted = BLFieldEncrypt.encrypt("secret", key, dekId)
        val wrongKey = BLFieldEncrypt.generateKey()
        assertThrows<AEADBadTagException> {
            BLFieldEncrypt.decrypt(encrypted, wrongKey)
        }
    }

    // ── Key Size Validation ─────────────────────────────────

    @Test
    fun `encrypt rejects short key`() {
        val shortKeyBytes = ByteArray(16) // 128-bit instead of 256-bit
        val shortKey = javax.crypto.spec.SecretKeySpec(shortKeyBytes, "AES")
        assertThrows<IllegalArgumentException> {
            BLFieldEncrypt.encrypt("test", shortKey, dekId)
        }
    }

    // ── Key from Hex ────────────────────────────────────────

    @Test
    fun `key from hex`() {
        val hex = "ab".repeat(32) // 64 hex chars = 32 bytes
        val hexKey = BLFieldEncrypt.keyFromHex(hex)
        val encrypted = BLFieldEncrypt.encrypt("test", hexKey, dekId)
        val decrypted = BLFieldEncrypt.decrypt(encrypted, hexKey)
        assertEquals("test", decrypted)
    }

    @Test
    fun `key from hex rejects invalid length`() {
        assertThrows<IllegalArgumentException> {
            BLFieldEncrypt.keyFromHex("aabb")
        }
    }

    // ── isEncrypted Type Guard ──────────────────────────────

    @Test
    fun `isEncrypted with valid field`() {
        val encrypted = BLFieldEncrypt.encrypt("test", key, dekId)
        assertTrue(BLFieldEncrypt.isEncrypted(encrypted))
    }

    @Test
    fun `isEncrypted with null field`() {
        assertFalse(BLFieldEncrypt.isEncrypted(null as BLEncryptedField?))
    }

    @Test
    fun `isEncrypted with map`() {
        val map = mapOf<String, Any?>(
            "__encrypted" to true,
            "v" to 1,
            "alg" to "aes-256-gcm",
            "ct" to "abcd",
            "iv" to "1234",
            "tag" to "5678",
            "dekId" to "dek_test",
        )
        assertTrue(BLFieldEncrypt.isEncrypted(map))
    }

    @Test
    fun `isEncrypted with incomplete map`() {
        val map = mapOf<String, Any?>("__encrypted" to true, "v" to 1)
        assertFalse(BLFieldEncrypt.isEncrypted(map))
    }

    @Test
    fun `isEncrypted with null map`() {
        assertFalse(BLFieldEncrypt.isEncrypted(null as Map<String, Any?>?))
    }

    // ── Hex Helpers ─────────────────────────────────────────

    @Test
    fun `byte array hex roundtrip`() {
        val original = byteArrayOf(0x00, 0x0f, 0xff.toByte(), 0xab.toByte(), 0xcd.toByte())
        val hex = original.toHexString()
        assertEquals("000fffabcd", hex)
        val restored = hex.hexToByteArray()
        assertArrayEquals(original, restored)
    }

    @Test
    fun `hex to byte array rejects odd length`() {
        assertThrows<IllegalArgumentException> {
            "a".hexToByteArray()
        }
    }
}
