package acquire.sdk.emv;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class FindAidPositionTest {

    // ----- helpers -----

    private static byte[] hexToBytes(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                    + Character.digit(hex.charAt(i + 1), 16));
        }
        return data;
    }

    /** Builds the [1-byte aidLen][AID bytes] sequence for one or more AIDs. */
    private static byte[] aidList(String... aidHexStrings) {
        int totalLen = 0;
        for (String aid : aidHexStrings) totalLen += 1 + aid.length() / 2;
        byte[] result = new byte[totalLen];
        int offset = 0;
        for (String aid : aidHexStrings) {
            byte[] aidBytes = hexToBytes(aid);
            result[offset++] = (byte) aidBytes.length;
            System.arraycopy(aidBytes, 0, result, offset, aidBytes.length);
            offset += aidBytes.length;
        }
        return result;
    }

    /**
     * Wraps an AID list byte array in a DF8410 TLV record.
     * Format: DF 84 10 [len] [aidList bytes]
     */
    private static byte[] wrapInDF8410(byte[] aidListBytes) {
        int len = aidListBytes.length;
        byte[] result = new byte[3 + 1 + len]; // tag(3) + len(1) + value
        result[0] = (byte) 0xDF;
        result[1] = (byte) 0x84;
        result[2] = (byte) 0x10;
        result[3] = (byte) len;
        System.arraycopy(aidListBytes, 0, result, 4, len);
        return result;
    }

    // ----- null / empty -----

    @Test
    public void nullData_returnsMinusOne() {
        assertEquals(-1, YsdkEmvProcessor.findAidPosition(null, "A000000912"));
    }

    @Test
    public void emptyData_returnsMinusOne() {
        assertEquals(-1, YsdkEmvProcessor.findAidPosition(new byte[0], "A000000912"));
    }

    // ----- no DF8410 tag -----

    @Test
    public void noDF8410Tag_returnsMinusOne() {
        // DF8411 0102  — count tag only, no AID list tag
        byte[] data = hexToBytes("DF84110102");
        assertEquals(-1, YsdkEmvProcessor.findAidPosition(data, "A000000912"));
    }

    // ----- single AID -----

    @Test
    public void singleAid_matchingPrefix_returnsOne() {
        byte[] data = wrapInDF8410(aidList("A000000912010100"));
        assertEquals(1, YsdkEmvProcessor.findAidPosition(data, "A000000912"));
    }

    @Test
    public void singleAid_exactPrefixLength_returnsOne() {
        byte[] data = wrapInDF8410(aidList("A000000912"));
        assertEquals(1, YsdkEmvProcessor.findAidPosition(data, "A000000912"));
    }

    @Test
    public void singleAid_noMatch_returnsMinusOne() {
        byte[] data = wrapInDF8410(aidList("A0000000030101"));
        assertEquals(-1, YsdkEmvProcessor.findAidPosition(data, "A000000912"));
    }

    // ----- multiple AIDs -----

    @Test
    public void multipleAids_matchAtPositionOne() {
        byte[] data = wrapInDF8410(aidList("A000000912010100", "A0000000041010"));
        assertEquals(1, YsdkEmvProcessor.findAidPosition(data, "A000000912"));
    }

    @Test
    public void multipleAids_matchAtPositionTwo() {
        byte[] data = wrapInDF8410(aidList("A0000000030101", "A000000912010100"));
        assertEquals(2, YsdkEmvProcessor.findAidPosition(data, "A000000912"));
    }

    @Test
    public void multipleAids_matchAtLastPosition() {
        byte[] data = wrapInDF8410(aidList("A0000000030101", "A0000000041010", "A000000912010100"));
        assertEquals(3, YsdkEmvProcessor.findAidPosition(data, "A000000912"));
    }

    @Test
    public void multipleAids_noMatch_returnsMinusOne() {
        byte[] data = wrapInDF8410(aidList("A0000000030101", "A0000000041010"));
        assertEquals(-1, YsdkEmvProcessor.findAidPosition(data, "A000000912"));
    }

    // ----- edge cases -----

    @Test
    public void prefixCaseInsensitive_lowercasePrefix_matches() {
        byte[] data = wrapInDF8410(aidList("A000000912010100"));
        assertEquals(1, YsdkEmvProcessor.findAidPosition(data, "a000000912"));
    }

    @Test
    public void malformedAidList_lengthExceedsBounds_returnsMinusOne() {
        // DF8410 claims 10 bytes of AID list but only 1 byte follows
        byte[] data = hexToBytes("DF841010" + "07");
        assertEquals(-1, YsdkEmvProcessor.findAidPosition(data, "A000000912"));
    }

    // ----- real-world TLLV input -----

    @Test
    public void realWorldTllv_jaywan_matchesPositionOne() {
        // DF8410 10 07A000000912212507A0000000041010  DF8411 01 02
        // AID-1: A0000009122125 (len=07) → matches A000000912 → position 1
        // AID-2: A0000000041010 (len=07)
        byte[] data = hexToBytes("DF84101007A000000912212507A0000000041010DF84110102");
        assertEquals(1, YsdkEmvProcessor.findAidPosition(data, "A000000912"));
    }

    @Test
    public void realWorldTllv_visa_matchesPositionTwo() {
        // Same payload, searching for Visa AID A0000000041010 → position 2
        byte[] data = hexToBytes("DF84101007A000000912212507A0000000041010DF84110102");
        assertEquals(2, YsdkEmvProcessor.findAidPosition(data, "A000000004"));
    }

    @Test
    public void realWorldTllv_unknownPrefix_returnsMinusOne() {
        byte[] data = hexToBytes("DF84101007A000000912212507A0000000041010DF84110102");
        assertEquals(-1, YsdkEmvProcessor.findAidPosition(data, "A000000099"));
    }

    @Test
    public void df8410_followedByOtherTags_stillFindsAid() {
        // DF8411 precedes DF8410 — parser must walk past it correctly
        byte[] df8411 = hexToBytes("DF84110102");
        byte[] df8410 = wrapInDF8410(aidList("A000000912010100", "A0000000041010"));
        byte[] data = new byte[df8411.length + df8410.length];
        System.arraycopy(df8411, 0, data, 0, df8411.length);
        System.arraycopy(df8410, 0, data, df8411.length, df8410.length);
        assertEquals(1, YsdkEmvProcessor.findAidPosition(data, "A000000912"));
    }
}
