package wang.yaojia.webterm.api.enroll

import java.math.BigInteger
import java.security.interfaces.ECPublicKey

/**
 * B4 · Pure encoder from a JCA [ECPublicKey] to the ANSI X9.63 uncompressed point
 * `0x04 || X || Y` that a P-256 `SubjectPublicKeyInfo` BIT STRING carries.
 *
 * Kept in the pure `:api-client` module (no Android dependency) so it is reused by BOTH the
 * JVM-unit-test software signer AND the framework `HardwareBackedKey` (`:client-tls-android`),
 * and so this security-load-bearing byte layout is unit-tested at JVM speed.
 */
public object EcPointEncoding {
    /** P-256 field element width in bytes (256 bits). */
    public const val P256_COORDINATE_BYTES: Int = 32

    /** Uncompressed-point prefix (`0x04`) per SEC 1 §2.3.3. */
    private const val UNCOMPRESSED_PREFIX: Byte = 0x04

    /**
     * Encode [publicKey]'s affine (x, y) as `0x04 || X(32) || Y(32)` (65 bytes). Each coordinate is
     * an unsigned big-endian integer left-padded (or, defensively, high-byte-trimmed) to exactly
     * [P256_COORDINATE_BYTES]. Throws [IllegalArgumentException] if a coordinate genuinely does not
     * fit 32 bytes (i.e. the key is not on a 256-bit curve).
     */
    public fun x963(publicKey: ECPublicKey): ByteArray {
        val point = publicKey.w
        val x = toFixedLengthUnsigned(point.affineX, P256_COORDINATE_BYTES)
        val y = toFixedLengthUnsigned(point.affineY, P256_COORDINATE_BYTES)
        val out = ByteArray(1 + P256_COORDINATE_BYTES * 2)
        out[0] = UNCOMPRESSED_PREFIX
        System.arraycopy(x, 0, out, 1, P256_COORDINATE_BYTES)
        System.arraycopy(y, 0, out, 1 + P256_COORDINATE_BYTES, P256_COORDINATE_BYTES)
        return out
    }

    /**
     * Convert a non-negative [value] to a big-endian byte array of exactly [length] bytes. A
     * `BigInteger` may carry a leading 0x00 sign byte (drop it) or be shorter than [length]
     * (left-pad with zeros). A value that needs MORE than [length] significant bytes is rejected —
     * silently truncating a coordinate would corrupt the key.
     */
    internal fun toFixedLengthUnsigned(value: BigInteger, length: Int): ByteArray {
        require(value.signum() >= 0) { "EC coordinate must be non-negative" }
        val raw = value.toByteArray() // big-endian, possibly with a leading 0x00 sign byte
        val start = if (raw.size > length && raw[0].toInt() == 0) 1 else 0
        val significant = raw.size - start
        require(significant <= length) { "EC coordinate does not fit $length bytes (got $significant)" }
        val out = ByteArray(length)
        System.arraycopy(raw, start, out, length - significant, significant)
        return out
    }
}
