package com.dangerousthings.aliro;

import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.Util;

/**
 * Scaffold for the Aliro step-up phase (spec §8.4). Lives under
 * {@link AliroAids#STEP_UP}.
 *
 * <p>This first pass only answers SELECT with a minimal FCI and rejects
 * unknown commands cleanly. The actual step-up protocol — mdoc
 * SessionData framing (DeviceRequest/DeviceResponse CBOR with
 * Aliro-remapped integer keys per Tables 8-21/8-22), ENVELOPE/GET
 * RESPONSE command chaining, and a fresh AES-256-GCM session keyed by
 * StepUpSKDevice/StepUpSKReader derived per spec §8.4.3 — comes in
 * follow-up iterations.
 *
 * <p>When that work lands, this applet will need access to the
 * {@code StepUpSK} (a 32-byte session-bound value produced by
 * {@link AliroApplet}'s derivation in §8.3.1.13). Since Java Card
 * installs distinct instances per AID, that state will be exchanged via
 * a shared package-private holder (not yet implemented).
 *
 * <p><b>Implementation notes for the Step-Up milestone (see
 * {@code docs/plans/2026-06-07-step-up-implementation.md}):</b>
 * <ol>
 *   <li><b>Opt 1 — Reuse AliroGcm.</b> Don't allocate a second
 *       {@link AliroGcm} for Step-Up; reuse {@link AliroApplet}'s instance
 *       via {@code aesKey.setKey(stepUpSKDevice)} between phases. Saves
 *       ~368 B transient. ExpeditedSK and StepUpSK are distinct so the
 *       key/IV uniqueness invariant holds across rekey.</li>
 *   <li><b>Opt 2 — Structural CBOR only.</b> The Access Document is opaque
 *       transport bytes to us; the reader does field-level parsing. We only
 *       need a structural CBOR codec (major type + length boundaries) for
 *       the SessionData wrapper and to extract {@code IssuerAuth} for
 *       verification. Do not implement field-level mdoc parsing — wastes
 *       bytecode and RAM. CBOR encoding MUST be deterministic per RFC 8949
 *       §4.2.1 anywhere we emit.</li>
 *   <li><b>Opt 3 — Stream-encrypt during ENVELOPE emit.</b> Don't buffer
 *       the entire encrypted DeviceResponse then chunk it; pipe plaintext
 *       through GCM as we emit ENVELOPE response chunks. Reduces transient
 *       footprint AND minimizes plaintext residence in RAM.</li>
 *   <li><b>Opt 4 — IssuerAuth verify cached at personalization.</b> See
 *       {@link CredentialStore#finalizeAccessDocument(short)} TODO — the
 *       COSE_Sign1 verify happens once at write time, the persistent
 *       {@code accessDocumentVerified} flag is checked here at read time.
 *       Saves ~100 ms per transaction. Trusts that the Credential Issuer
 *       trust anchor is fixed for the card's lifetime — true for DT's
 *       implantable target but document the limitation.</li>
 * </ol>
 *
 * <p>Also when this lands, revisit {@link AliroApplet}'s {@code INS_EXCHANGE}
 * stub: with real Step-Up at this AID, the stub becomes either (a) unused
 * if {@code signaling_bitmap} bit 2 stays set and spec-conformant readers
 * route EXCHANGE here, or (b) replaceable with a proper "Reader Status
 * sub-event" reply if X-CUBE-ALIRO's vendor bug persists.
 */
public class StepUpApplet extends Applet {

    private static final byte CLA_PROPRIETARY = (byte) 0x80;

    public static void install(byte[] bArray, short bOffset, byte bLength) {
        StepUpApplet applet = new StepUpApplet();
        if (bArray == null || bLength == 0) {
            applet.register();
        } else {
            applet.register(bArray, (short) (bOffset + 1), bArray[bOffset]);
        }
    }

    @Override
    public boolean select() {
        return true;
    }

    @Override
    public void process(APDU apdu) {
        if (selectingApplet()) {
            sendStepUpFci(apdu);
            return;
        }

        byte[] buf = apdu.getBuffer();
        if (buf[ISO7816.OFFSET_CLA] != CLA_PROPRIETARY) {
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        }

        // Until the mdoc DeviceRequest pipeline lands, every proprietary INS
        // is unrecognised. ENVELOPE + GET RESPONSE handlers plug in here.
        ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
    }

    /**
     * Minimal FCI for step-up SELECT. The spec (§10.2.1.2) allows the UD to
     * advertise supported APDU command/response sizes here; those get added
     * when we implement ENVELOPE/GET RESPONSE.
     *
     * <pre>
     * 6F L
     *   84 09 [step-up AID]
     * </pre>
     */
    private void sendStepUpFci(APDU apdu) {
        byte[] buf = apdu.getBuffer();
        short off = 0;
        buf[off++] = 0x6F;
        short outerLenPos = off++;

        buf[off++] = (byte) 0x84;
        buf[off++] = (byte) AliroAids.STEP_UP.length;
        off = Util.arrayCopyNonAtomic(AliroAids.STEP_UP, (short) 0,
                                      buf, off, (short) AliroAids.STEP_UP.length);

        buf[outerLenPos] = (byte) (off - 2);
        apdu.setOutgoingAndSend((short) 0, off);
    }
}
