r"""Provide functions for the generation of the Application
Cryptograms (TC, ARQC, or AAC) generated by the ICC and the
Authorisation Response Cryptogram (ARPC) generated by the
issuer and verified by the ICC.

ARQC:

    >>> import pyemv
    >>> session_key = bytes.fromhex('29B33180E567CE38EA4CBC9D753B0E61')
    >>> ac_data = bytes.fromhex('0123456789ABCDEF0123456789ABCDEF')
    >>> arqc = pyemv.ac.generate_ac(session_key, ac_data)
    >>> arqc.hex().upper()
    'FA624250B008B59A'

ARPC:

    >>> import pyemv
    >>> session_key = bytes.fromhex('29B33180E567CE38EA4CBC9D753B0E61')
    >>> arpc_rc = bytes.fromhex('0000')
    >>> arpc = pyemv.ac.generate_arpc_1(session_key, arqc, arpc_rc)
    >>> arpc.hex().upper()
    '45D4255EEF10C920'
    >>> csu = bytes.fromhex('00000000')
    >>> arpc = pyemv.ac.generate_arpc_2(session_key, arqc, csu)
    >>> arpc.hex().upper()
    'CB56FA40'
"""

import typing as _typing
from enum import Enum as _Enum

from pyemv.mac import mac_iso9797_3 as _mac_iso9797_3
from pyemv.tools import encrypt_tdes_cbc as _encrypt_tdes_cbc
from pyemv.tools import xor as _xor

__all__ = [
    "PaddingType",
    "generate_ac",
    "generate_arpc_1",
    "generate_arpc_2",
]


class PaddingType(_Enum):
    VISA = 1
    EMV = 2


def generate_ac(
    sk_ac: bytes,
    data: bytes,
    padding_type: _typing.Optional[PaddingType] = None,
    length: _typing.Optional[int] = None,
) -> bytes:
    r"""First and second AC Generation using 8-byte block chiper
    Triple DES with ISO/IEC 9797-1 Algorithm 3. Same process for

        - Authorisation Request Cryptogram (ARQC)
        - Transaction Cryptogram (TC)
        - Application Authentication Cryptogram (AAC)

    Parameters
    ----------
    sk_ac : bytes
        Binary ICC Session Key for AC. Has to be a valid DES key.
    data : bytes
        Binary data from tags used in AC generation. Minimum recommended
        tags by EMV Book2 Application Cryptogram Data Selection.
        +------+-----+----------+---------------------------------+
        | Tag  | Len | Source   | Data                            |
        +------+-----+----------+---------------------------------+
        | 9F02 |   6 | Terminal | Amount, Authorized              |
        +------+-----+----------+---------------------------------+
        | 9F03 |   6 | Terminal | Amount, Other                   |
        +------+-----+----------+---------------------------------+
        | 9F1A |   2 | Terminal | Terminal Country Code           |
        +------+-----+----------+---------------------------------+
        | 95   |   5 | Terminal | Terminal Verification Results   |
        +------+-----+----------+---------------------------------+
        | 5F2A |   2 | Terminal | Transaction Currency Code       |
        +------+-----+----------+---------------------------------+
        | 9A   |   3 | Terminal | Transaction Date                |
        +------+-----+----------+---------------------------------+
        | 9C   |   1 | Terminal | Transaction Type                |
        +------+-----+----------+---------------------------------+
        | 9F37 |   4 | Terminal | Unpredictable Number            |
        +------+-----+----------+---------------------------------+
        | 82   |   2 | ICC      | Application Interchange Profile |
        +------+-----+----------+---------------------------------+
        | 9F36 |   2 | ICC      | Application Transaction Counter |
        +------+-----+----------+---------------------------------+
    padding_type : PaddingType, optional
        Padding method applied to the `data` (default EMV).

            - VISA = ISO/IEC 9797-1 method 1.
              Generally used by Visa schemes; not all of them.
            - EMV = ISO/IEC 9797-1 method 2.
              Generally used by other EMV schemes.

    length : int, optional
        Desired length of AC [4 <= `length` <= 8] (default 8 bytes).

    Returns
    -------
    ac : bytes
        Returns an `length`-byte cryptogram (ARQC, TC, AAC).

    Raises
    ------
    ValueError
        Session Key must be a double length DES key
    TypeError
        Padding type must be PaddingType Enum

    Notes
    -----
    The cryptogram takes as input a number of terminal and
    card data elements which are encrypted using the session key.
    The same elements must be sent to the host in the authorisation
    request so that the host can validate the cryptogram (also sent).

    After the terminal/reader receives the host generated ARPC in the
    response message it will send it to the card in the 2nd Generate AC
    command. The card will validate the ARPC and calculate another
    cryptogram (TC or AAC) which is returned in the 2nd Generate AC
    command. The calculation of this cryptogram is the same as for the
    1st cryptogram, using the same data input (although some of the
    values will be different, i.e. the CVR values).

    For further details see also:
        - EMV 4.3 Book 2 Section 8.1 Application Cryptogram Generation
        - EMV 4.3 Book 2 Annex A 1.2

    Examples
    --------
    >>> from pyemv import ac
    >>> sk_ac = bytes.fromhex("AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB")
    >>> data = bytes.fromhex(
    ...     "000000002000" + # 9F02
    ...     "000000000000" + # 9F03
    ...     "0124" +         # 9F1A
    ...     "0000008000" +   # 95
    ...     "0124" +         # 5F2A
    ...     "110309" +       # 9A
    ...     "00" +           # 9C
    ...     "3804823E" +     # 9F37
    ...     "5800" +         # 82
    ...     "0001")          # 9F36
    >>> arqc = ac.generate_ac(sk_ac, data)
    >>> arqc.hex().upper()
    '3B76CF10FECD8789'
    """
    if len(sk_ac) != 16:
        raise ValueError("Session Key must be a double length DES key")

    if padding_type is None:
        padding_type = PaddingType.EMV

    if not isinstance(padding_type, PaddingType):
        raise TypeError(
            "Padding type must be PaddingType Enum, "
            f"not {padding_type.__class__.__name__}"
        )

    return _mac_iso9797_3(sk_ac[:8], sk_ac[-8:], data, padding_type.value, length)


def generate_arpc_1(sk_ac: bytes, arqc: bytes, arpc_rc: bytes) -> bytes:
    r"""Generate Authorisation Response Cryptogram (ARPC) using method 1.
    Method for the generation of an 8-byte ARPC consists of applying
    the Triple-DES algorithm to:

        - 8-byte binary ARQC
        - 2-byte binary Authorisation Response Code (ARPC-RC)

    Parameters
    ----------
    sk_ac : bytes
        Binary ICC Session Key for AC. Has to be a valid DES key.
    arqc : bytes
        Binary 8-byte Authorisation Request Cryptogram (ARQC).
    arpc_rc : bytes
        Binary 2-byte ARPC response code.

    Returns
    -------
    arpc : bytes
        Returns binary 8-byte Authorisation Response Cryptogram (ARPC).
        The resulting issuer authentication data (tag 91) is:

            91 || Len || ARPC || ARPC-RC

    Raises
    ------
    ValueError
        Session Key must be a double length DES key
        ARQC must be 8 bytes long
        ARPC-RC must be 2 bytes long

    Notes
    -----
    After the host has validated the cryptogram sent by the terminal/reader
    in the authorisation request it will generate an Authorisation Response
    cryptogram (ARPC) which will then be returned in the response message
    along with the ARPC response code and ultimately validated by the card.

    For further details see also:
        - EMV 4.3 Book 2 Section 8.2 Issuer Authentication
        - EMV 4.3 Book 2 Section 8.2.1 ARPC Method 1

    Examples
    --------
    >>> from pyemv import ac
    >>> sk_ac = bytes.fromhex("AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB")
    >>> arqc = bytes.fromhex("1234567890ABCDEF")
    >>> arpc_rc = bytes.fromhex("0000")
    >>> arpc = ac.generate_arpc_1(sk_ac, arqc, arpc_rc)
    >>> arpc.hex().upper()
    'F5E6F44147E2F1B0'
    """
    if len(sk_ac) != 16:
        raise ValueError("Session Key must be a double length DES key")

    if len(arqc) != 8:
        raise ValueError("ARQC must be 8 bytes long")

    if len(arpc_rc) != 2:
        raise ValueError("ARPC-RC must be 2 bytes long")

    return _encrypt_tdes_cbc(
        sk_ac, b"\x00\x00\x00\x00\x00\x00\x00\x00", _xor(arpc_rc + (b"\00" * 6), arqc)
    )


def generate_arpc_2(
    sk_ac: bytes,
    arqc: bytes,
    csu: bytes,
    prop_auth_data: _typing.Optional[bytes] = None,
) -> bytes:
    r"""Generate Authorisation Response Cryptogram (ARPC) using method 2.
    Method for the generation of a 4-byte ARPC consists of applying
    ISO/IEC 9797-1 MAC algorithm 3 to:

        - 8-byte binary ARQC
        - 4-byte binary Card Status Update (CSU)
        - 0-8 byte binary Proprietary Authentication Data

    Parameters
    ----------
    sk_ac : bytes
        Binary ICC Session Key for AC. Has to be a valid DES key.
    arqc : bytes
        Binary 8-byte Authorisation Request Cryptogram (ARQC).
    csu : bytes
        Binary 4-byte Card Status Update (CSU).
    prop_auth_data : bytes, optional
        Binary 0-8 byte Proprietary Authentication Data.

    Returns
    -------
    arpc : bytes
        Returns binary 4-byte Authorisation Response Cryptogram (ARPC).
        The resulting issuer authentication data (tag 91) is:

            91 || Len || ARPC || CSU || { Proprietary Authentication Data }

    Raises
    ------
    ValueError
        Session Key must be a double length DES key
        ARQC must be 8 bytes long
        CSU must be 4 bytes long
        Proprietary Authentication Data must be 0-8 bytes long

    Notes
    -----
    After the host has validated the cryptogram sent by the terminal/reader
    in the authorisation request it will generate an Authorisation Response
    cryptogram (ARPC) which will then be returned in the response message
    along with the ARPC response code and ultimately validated by the card.

    For further details see also:
        - EMV 4.3 Book 2 Section 8.2 Issuer Authentication
        - EMV 4.3 Book 2 Section 8.2.2 ARPC Method 2
        - EMV 4.3 Book 2 Annex A 1.2.1

    Examples
    --------
    >>> from pyemv import ac
    >>> sk_ac = bytes.fromhex("AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB")
    >>> arqc = bytes.fromhex("1234567890ABCDEF")
    >>> csu = bytes.fromhex("00000000")
    >>> arpc = ac.generate_arpc_2(sk_ac, arqc, csu)
    >>> arpc.hex().upper()
    '9308BEBC'
    """
    if len(sk_ac) != 16:
        raise ValueError("Session Key must be a double length DES key")

    if prop_auth_data is None:
        prop_auth_data = b""

    if len(arqc) != 8:
        raise ValueError("ARQC must be 8 bytes long")

    if len(csu) != 4:
        raise ValueError("CSU must be 4 bytes long")

    if len(prop_auth_data) > 8:
        raise ValueError("Proprietary Authentication Data must be 0-8 bytes long")

    return _mac_iso9797_3(sk_ac[:8], sk_ac[-8:], arqc + csu + prop_auth_data, 2, 4)
