package no.zyon.shade.blob

import com.google.crypto.tink.subtle.Ed25519Sign
import no.zyon.shade.crypto.CryptoProvider

/**
 * V4.9 — relay-side encrypted blob primitive: deterministic
 * derivations from a 32-byte master key + per-app namespace string.
 *
 * Mirror `@shade/storage-encrypted/crypto/kdf.ts` byte-for-byte.
 * Reference vectors: `test-vectors/blob-storage.json`.
 *
 * Three independent 32-byte derivations:
 *
 *   slotId  = HKDF(masterKey, info=`shade-blob-slot-v1:<app>`)  // relay-visible opaque ID
 *   blobKey = HKDF(masterKey, info=`shade-blob-key-v1:<app>`)   // AEAD key for the blob
 *   sigSeed = HKDF(masterKey, info=`shade-blob-sig-v1:<app>`)   // Ed25519 owner signing seed
 *
 * `app` is a caller-supplied namespace string — distinct apps under
 * the same master MUST pass different values (e.g. `prism-profile-v1`)
 * so they don't collide on the same slot.
 *
 * The signing seed is an Ed25519 *seed* in the @noble/curves convention:
 * `pubkey = Ed25519.publicFromSeed(seed)` is what the relay TOFU-stores
 * on the first PUT and verifies subsequent writes against.
 */

private const val SLOT_INFO_PREFIX = "shade-blob-slot-v1:"
private const val BLOB_KEY_INFO_PREFIX = "shade-blob-key-v1:"
private const val SIG_SEED_INFO_PREFIX = "shade-blob-sig-v1:"

private const val DERIVED_LEN = 32

/** Lower-hex 64-char slotId derived deterministically from the master key. */
fun deriveBlobSlotId(crypto: CryptoProvider, masterKey: ByteArray, app: String): ByteArray {
    require(masterKey.size == 32) { "masterKey must be 32 bytes" }
    require(app.isNotEmpty()) { "app namespace must be non-empty" }
    return crypto.hkdf(
        masterKey,
        ByteArray(0),
        (SLOT_INFO_PREFIX + app).toByteArray(Charsets.UTF_8),
        DERIVED_LEN,
    )
}

/** AEAD key for sealing/opening the blob. The slotId hex is bound as AAD. */
fun deriveBlobKey(crypto: CryptoProvider, masterKey: ByteArray, app: String): ByteArray {
    require(masterKey.size == 32) { "masterKey must be 32 bytes" }
    require(app.isNotEmpty()) { "app namespace must be non-empty" }
    return crypto.hkdf(
        masterKey,
        ByteArray(0),
        (BLOB_KEY_INFO_PREFIX + app).toByteArray(Charsets.UTF_8),
        DERIVED_LEN,
    )
}

/**
 * 32-byte Ed25519 signing seed. The pubkey, derived deterministically
 * from the seed, is what the relay TOFU-stores on the first PUT.
 */
fun deriveBlobSigningSeed(crypto: CryptoProvider, masterKey: ByteArray, app: String): ByteArray {
    require(masterKey.size == 32) { "masterKey must be 32 bytes" }
    require(app.isNotEmpty()) { "app namespace must be non-empty" }
    return crypto.hkdf(
        masterKey,
        ByteArray(0),
        (SIG_SEED_INFO_PREFIX + app).toByteArray(Charsets.UTF_8),
        DERIVED_LEN,
    )
}

/**
 * Recover the Ed25519 public key from a 32-byte seed. Mirrors
 * `@shade/crypto-web`'s `ed25519PublicKeyFromSeed`. Tink's
 * `Ed25519Sign.KeyPair.newKeyPairFromSeed(seed)` exposes both halves;
 * we discard the private half here.
 */
fun ed25519PublicKeyFromSeed(seed: ByteArray): ByteArray {
    require(seed.size == 32) { "Ed25519 seed must be 32 bytes" }
    return Ed25519Sign.KeyPair.newKeyPairFromSeed(seed).publicKey
}

/** Convert a 32-byte slotId into the lowercase-hex wire form (64 chars). */
fun slotIdToHex(slotId: ByteArray): String {
    require(slotId.size == 32) { "slotId must be 32 bytes" }
    return slotId.joinToString("") { "%02x".format(it) }
}
