"""
Smart Card Communication Library

Unified cross-platform library for smart card communication.

REQUIRES: pyscard (pip install pyscard)
See README.md for platform setup instructions.
"""

import platform
import sys

# Constants
DEFAULT_IOCTL_CODE = 3500  # CCID escape command IOCTL code for smart card readers

# Try to import smartcard library with platform-specific error handling
try:
    from smartcard.System import readers
    from smartcard.util import toHexString
    from smartcard.CardConnection import CardConnection
    from smartcard.scard import (
    SCARD_SCOPE_USER, SCARD_S_SUCCESS, SCARD_SHARE_DIRECT,
    SCARD_PROTOCOL_T0, SCARD_PROTOCOL_T1, SCARD_PROTOCOL_UNDEFINED,
    SCARD_LEAVE_CARD, SCARD_CTL_CODE, SCardEstablishContext,
    SCardConnect, SCardControl, SCardDisconnect, SCardReleaseContext,
    SCardGetErrorMessage
)
    import smartcard.Exceptions
except ImportError:
    error_msg = "pyscard library not found!\n\n"
    system = platform.system()
    if system == "Darwin":  # macOS
        error_msg += "For macOS, install using:\n"
        error_msg += "  pip install pyscard\n\n"
        error_msg += "Note: PCSC framework should be pre-installed on macOS."
    elif system == "Linux":  # Ubuntu/Linux
        error_msg += "For Ubuntu/Linux, first install system dependencies:\n"
        error_msg += "  sudo apt-get install pcscd libpcsclite1 libpcsclite-dev\n"
        error_msg += "  sudo systemctl start pcscd\n"
        error_msg += "  sudo systemctl enable pcscd\n\n"
        error_msg += "Then install Python package:\n"
        error_msg += "  pip install pyscard"
    else:  # Windows
        error_msg += "For Windows, install using:\n"
        error_msg += "  pip install pyscard\n\n"
        error_msg += "Note: Windows Smart Card API should be available by default."

    print(error_msg)
    if sys.stdout.isatty():
        input("Press Enter to exit...")
    sys.exit(1)


def list_readers():
    """
    List all available smart card readers.

    Returns:
        List of reader objects.
    """
    try:
        reader_list = readers()
        return list(reader_list)
    except smartcard.Exceptions.NoReadersException:
        return []
    except Exception as e:
        print(f"Error listing readers: {str(e)}")
        return []


def parse_command_string(hex_string):
    """
    Parse a hexadecimal string into a list of bytes.

    Args:
        hex_string: Hex string (spaces optional)

    Returns:
        List of integers representing bytes

    Raises:
        ValueError: If string contains invalid characters or has odd length
    """
    hex_string = hex_string.replace(" ", "")
    if not all(c in '0123456789ABCDEFabcdef' for c in hex_string):
        raise ValueError("Invalid characters in hex string. Use hexadecimal values only.")
    if len(hex_string) % 2 != 0:
        raise ValueError("Hex string length must be even (each byte is 2 hex digits).")

    return [int(hex_string[i:i + 2], 16) for i in range(0, len(hex_string), 2)]


# ============================================================================
# CONNECTION FUNCTIONS
# ============================================================================

def connect_to_reader(reader, mode='share', protocol='auto'):
    """
    Connect to a smart card reader.

    Args:
        reader: Reader object from list_readers() or reader name string.
        mode: 'share' for APDU communication (default), 'direct' for escape commands.
        protocol: 'auto' (default), 't0t1', or 'undefined'.

    Returns:
        tuple: (connection, error_msg, context).
    """
    if reader is None:
        return None, "Reader is None.", None

    protocol_map = {
        't0t1': SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
        'undefined': SCARD_PROTOCOL_UNDEFINED,
        'auto': (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1) if platform.system() == "Darwin" else SCARD_PROTOCOL_UNDEFINED
    }
    protocol_value = protocol_map.get(protocol, protocol_map['auto'])

    if mode == 'share':
        try:
            connection = reader.createConnection()
            connection.connect(CardConnection.T0_protocol | CardConnection.T1_protocol if protocol_value == (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1) else None)
            return connection, None, None
        except smartcard.Exceptions.NoCardException:
            return None, "No card in reader. Please insert a smart card.", None
        except Exception as e:
            return None, f"Connection error: {str(e)}", None

    elif mode == 'direct':
        hcontext = None
        connection_successful = False
        try:
            hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
            if hresult != SCARD_S_SUCCESS:
                return None, f"Context setup failed: {SCardGetErrorMessage(hresult)}", None

            reader_name = str(reader) if not isinstance(reader, str) else reader
            hresult, hcard, _ = SCardConnect(hcontext, reader_name, SCARD_SHARE_DIRECT, protocol_value)

            if hresult == SCARD_S_SUCCESS:
                connection_successful = True
                return hcard, None, hcontext
            else:
                return None, f"Failed to connect: {SCardGetErrorMessage(hresult)}", None
        except Exception as e:
            return None, f"Exception connecting: {str(e)}", None
        finally:
            if hcontext and not connection_successful:
                try:
                    SCardReleaseContext(hcontext)
                except:
                    pass

    else:
        return None, f"Invalid mode: {mode}. Use 'share' or 'direct'.", None


def disconnect_reader(connection, hcontext=None):
    """
    Disconnects from a smart card reader and releases context if applicable.

    Args:
        connection: CardConnection object (APDU mode) or card handle (escape mode).
        hcontext: The established context handle for direct connections.
    """
    try:
        if connection is None:
            return
        # pyscard returns CardConnectionDecorator, not CardConnection directly
        if hasattr(connection, 'disconnect'):
            connection.disconnect()
        else:
            SCardDisconnect(connection, SCARD_LEAVE_CARD)
    except Exception as e:
        print(f"Error disconnecting from reader: {str(e)}")
    finally:
        if hcontext:
            try:
                SCardReleaseContext(hcontext)
            except Exception as e:
                print(f"Error releasing context: {str(e)}")

# ============================================================================
# COMMAND FUNCTIONS
# ============================================================================

def send_command(connection, command, mode='apdu', ioctl_code=DEFAULT_IOCTL_CODE):
    """
    Send a command to a smart card device.

    Args:
        connection: CardConnection object (APDU mode) or card handle (escape mode).
        command: Hex string (APDU mode) or byte array/list (escape mode).
        mode: 'apdu' for APDU commands, 'escape' for escape commands.
        ioctl_code: IOCTL code for escape commands (default: 3500 - CCID escape).

    Returns:
        For APDU mode: (success, response, sw1, sw2, error_msg).
        For escape mode: (success, response, error_msg).
    """
    if mode not in ('apdu', 'escape'):
        raise ValueError(f"Invalid mode: {mode}. Must be 'apdu' or 'escape'")

    if connection is None:
        error_msg = "No connection. Please connect to a reader first."
        return (False, [], 0, 0, error_msg) if mode == 'apdu' else (False, [], error_msg)

    try:
        if mode == 'apdu':
            apdu = parse_command_string(command)
            response, sw1, sw2 = connection.transmit(apdu)
            return True, response, sw1, sw2, None
        elif mode == 'escape':
            IOCTL_CCID_ESCAPE = SCARD_CTL_CODE(ioctl_code)
            hresult, response = SCardControl(connection, IOCTL_CCID_ESCAPE, command)
            return (True, response, None) if hresult == SCARD_S_SUCCESS else (False, response, SCardGetErrorMessage(hresult))
        else:
            error_msg = f"Invalid mode: {mode}. Use 'apdu' or 'escape'."
            return (False, [], 0, 0, error_msg) if mode == 'apdu' else (False, [], error_msg)
    except ValueError as e:
        error_msg = f"Invalid command format: {str(e)}"
        return (False, [], 0, 0, error_msg) if mode == 'apdu' else (False, [], error_msg)
    except Exception as e:
        error_msg = f"Transmission error: {str(e)}"
        return (False, [], 0, 0, error_msg) if mode == 'apdu' else (False, [], error_msg)


# ============================================================================
# UTILITY FUNCTIONS
# ============================================================================

