package com.dangerousthings.aliro;

import com.licel.jcardsim.smartcardio.CardSimulator;
import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import javacard.framework.AID;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * Test-only helper that installs {@link PersonalizationApplet}, pushes the
 * Access Credential long-term key pair and reader public key into the
 * shared {@link CredentialStore}, and COMMITs so later Aliro flow tests
 * can assume a fully-provisioned card.
 */
final class ProvisioningHelper {

    private static final byte CLA = (byte) 0x80;
    private static final byte INS_SET_CRED_PRIV = (byte) 0x20;
    private static final byte INS_SET_CRED_PUBK = (byte) 0x21;
    private static final byte INS_SET_READER_PUBK = (byte) 0x22;
    private static final byte INS_COMMIT = (byte) 0x2C;

    private ProvisioningHelper() {}

    /** Installs PersonalizationApplet, provisions both keys, commits, and deselects. */
    static void provision(CardSimulator sim, KeyPair credential, ECPublicKey readerPub) {
        AID aid = new AID(AliroAids.PROVISIONING, (short) 0, (byte) AliroAids.PROVISIONING.length);
        sim.installApplet(aid, PersonalizationApplet.class);

        CommandAPDU select = new CommandAPDU(0x00, 0xA4, 0x04, 0x00, AliroAids.PROVISIONING, 256);
        assertEquals(0x9000, sim.transmitCommand(select).getSW(), "SELECT provisioning AID");

        byte[] credPriv = toFixed32(((ECPrivateKey) credential.getPrivate()).getS().toByteArray());
        send(sim, INS_SET_CRED_PRIV, credPriv);

        byte[] credPubRaw = extractXY((ECPublicKey) credential.getPublic());
        send(sim, INS_SET_CRED_PUBK, credPubRaw);

        byte[] readerPubRaw = extractXY(readerPub);
        send(sim, INS_SET_READER_PUBK, readerPubRaw);

        send(sim, INS_COMMIT, new byte[0]);
    }

    private static void send(CardSimulator sim, byte ins, byte[] data) {
        CommandAPDU c = new CommandAPDU(CLA & 0xFF, ins & 0xFF, 0x00, 0x00, data, 256);
        ResponseAPDU r = sim.transmitCommand(c);
        assertEquals(0x9000, r.getSW(), "provisioning INS 0x" + Integer.toHexString(ins & 0xFF) + " failed");
    }

    /** Returns the 64-byte x||y representation (no 0x04 prefix). */
    static byte[] extractXY(ECPublicKey pub) {
        byte[] x = toFixed32(pub.getW().getAffineX().toByteArray());
        byte[] y = toFixed32(pub.getW().getAffineY().toByteArray());
        byte[] out = new byte[64];
        System.arraycopy(x, 0, out, 0, 32);
        System.arraycopy(y, 0, out, 32, 32);
        return out;
    }

    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;
    }
}
