package no.zyon.shade.crypto

import com.google.crypto.tink.subtle.Ed25519Sign
import com.google.crypto.tink.subtle.Ed25519Verify
import com.google.crypto.tink.subtle.Hkdf
import com.google.crypto.tink.subtle.X25519
import java.nio.ByteBuffer
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.Mac
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec

/**
 * CryptoProvider backed by Google Tink + javax.crypto.
 *
 * Must produce byte-identical output to @shade/crypto-web for the same
 * inputs, otherwise cross-platform messaging breaks.
 */
class TinkProvider : CryptoProvider {
    private val random = SecureRandom()

    // ─── X25519 ────────────────────────────────────────────────

    override fun generateX25519KeyPair(): Pair<ByteArray, ByteArray> {
        val privateKey = X25519.generatePrivateKey()
        val publicKey = X25519.publicFromPrivate(privateKey)
        return publicKey to privateKey
    }

    override fun x25519(privateKey: ByteArray, publicKey: ByteArray): ByteArray {
        return X25519.computeSharedSecret(privateKey, publicKey)
    }

    // ─── Ed25519 ───────────────────────────────────────────────

    override fun generateEd25519KeyPair(): Pair<ByteArray, ByteArray> {
        val keyPair = Ed25519Sign.KeyPair.newKeyPair()
        return keyPair.publicKey to keyPair.privateKey
    }

    override fun sign(privateKey: ByteArray, message: ByteArray): ByteArray {
        val signer = Ed25519Sign(privateKey)
        return signer.sign(message)
    }

    override fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray): Boolean {
        return try {
            Ed25519Verify(publicKey).verify(signature, message)
            true
        } catch (_: Exception) {
            false
        }
    }

    // ─── AES-256-GCM ──────────────────────────────────────────

    override fun aesGcmEncrypt(
        key: ByteArray,
        plaintext: ByteArray,
        aad: ByteArray?,
    ): Pair<ByteArray, ByteArray> {
        val nonce = randomBytes(12)
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        val spec = GCMParameterSpec(128, nonce)
        cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), spec)
        if (aad != null) cipher.updateAAD(aad)
        val ciphertext = cipher.doFinal(plaintext)
        return ciphertext to nonce
    }

    override fun aesGcmDecrypt(
        key: ByteArray,
        ciphertext: ByteArray,
        nonce: ByteArray,
        aad: ByteArray?,
    ): ByteArray {
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        val spec = GCMParameterSpec(128, nonce)
        cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), spec)
        if (aad != null) cipher.updateAAD(aad)
        return cipher.doFinal(ciphertext)
    }

    // ─── Key Derivation ────────────────────────────────────────

    override fun hkdf(ikm: ByteArray, salt: ByteArray, info: ByteArray, length: Int): ByteArray {
        return Hkdf.computeHkdf("HMACSHA256", ikm, salt, info, length)
    }

    override fun hmacSha256(key: ByteArray, data: ByteArray): ByteArray {
        val mac = Mac.getInstance("HmacSHA256")
        mac.init(SecretKeySpec(key, "HmacSHA256"))
        return mac.doFinal(data)
    }

    // ─── Random ────────────────────────────────────────────────

    override fun randomBytes(length: Int): ByteArray {
        val buf = ByteArray(length)
        random.nextBytes(buf)
        return buf
    }

    override fun randomUint32(): Int {
        val buf = randomBytes(4)
        return ByteBuffer.wrap(buf).int
    }

    // ─── Hardening ─────────────────────────────────────────────

    override fun constantTimeEqual(a: ByteArray, b: ByteArray): Boolean {
        if (a.size != b.size) return false
        var diff = 0
        for (i in a.indices) {
            diff = diff or (a[i].toInt() xor b[i].toInt())
        }
        return diff == 0
    }

    override fun zeroize(buf: ByteArray) {
        buf.fill(0)
    }
}
