package com.dangerousthings.aliro;

import java.io.ByteArrayOutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;

/**
 * Test-only builder for Aliro AUTH0 command payloads, per spec Table 8-4.
 *
 * <pre>
 *   0x41 01 [command_parameters]
 *   0x42 01 [authentication_policy]
 *   0x5C 02 [expedited_phase_protocol_version]
 *   0x87 41 04 || x || y       (reader_ePubK uncompressed, 65 bytes)
 *   0x4C 10 [transaction_identifier]
 *   0x4D 20 [reader_group_id 16 || reader_group_sub_id 16]
 * </pre>
 */
final class Auth0Command {

    static final byte CLA = (byte) 0x80;
    static final byte INS = (byte) 0x80;

    static final short PROTOCOL_V1_0 = (short) 0x0100;

    static final byte CMD_PARAM_STANDARD = (byte) 0x00;
    static final byte CMD_PARAM_FAST = (byte) 0x01;

    static final byte AUTH_POLICY_NONE = (byte) 0x00;

    private Auth0Command() {}

    /** Generates a fresh P-256 keypair for use by tests simulating the reader. */
    static KeyPair generateEphemeralKeyPair() {
        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
            kpg.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
            return kpg.generateKeyPair();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /** Encodes a P-256 public key to uncompressed 65-byte form (0x04 || x || y). */
    static byte[] encodeUncompressed(ECPublicKey pub) {
        byte[] x = toFixed32(pub.getW().getAffineX().toByteArray());
        byte[] y = toFixed32(pub.getW().getAffineY().toByteArray());
        byte[] out = new byte[65];
        out[0] = 0x04;
        System.arraycopy(x, 0, out, 1, 32);
        System.arraycopy(y, 0, out, 33, 32);
        return out;
    }

    private static byte[] toFixed32(byte[] b) {
        if (b.length == 32) return b;
        byte[] out = new byte[32];
        if (b.length > 32) {
            System.arraycopy(b, b.length - 32, out, 0, 32);
        } else {
            System.arraycopy(b, 0, out, 32 - b.length, b.length);
        }
        return out;
    }

    /** Builds a standard-phase AUTH0 command data field with the given parts. */
    static byte[] buildStandardData(
            byte[] readerEphemPubKeyUncompressed,
            byte[] readerGroupId,
            byte[] readerGroupSubId,
            byte[] transactionId) {
        if (readerEphemPubKeyUncompressed.length != 65) {
            throw new IllegalArgumentException("reader_ePubK must be 65 bytes (uncompressed)");
        }
        if (readerGroupId.length != 16 || readerGroupSubId.length != 16) {
            throw new IllegalArgumentException("reader_group_{id,sub_id} must be 16 bytes each");
        }
        if (transactionId.length != 16) {
            throw new IllegalArgumentException("transaction_identifier must be 16 bytes");
        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        writeTlv(out, 0x41, new byte[] { CMD_PARAM_STANDARD });
        writeTlv(out, 0x42, new byte[] { AUTH_POLICY_NONE });
        writeTlv(out, 0x5C, new byte[] {
            (byte) ((PROTOCOL_V1_0 >> 8) & 0xFF),
            (byte) (PROTOCOL_V1_0 & 0xFF)
        });
        writeTlv(out, 0x87, readerEphemPubKeyUncompressed);
        writeTlv(out, 0x4C, transactionId);
        byte[] readerIdentifier = new byte[32];
        System.arraycopy(readerGroupId, 0, readerIdentifier, 0, 16);
        System.arraycopy(readerGroupSubId, 0, readerIdentifier, 16, 16);
        writeTlv(out, 0x4D, readerIdentifier);
        return out.toByteArray();
    }

    private static void writeTlv(ByteArrayOutputStream out, int tag, byte[] value) {
        out.write(tag);
        out.write(value.length);
        out.write(value, 0, value.length);
    }
}
