#!/usr/bin/env python3
"""
Local NFC writer proxy for nfc_writer.html.

On Windows this uses the built-in PC/SC API from WinSCard.dll, so no Python
package is required. On other platforms it falls back to pyscard.

Run:
    py nfc_proxy.py

Then open nfc_writer.html. The browser will use http://127.0.0.1:8787 when
Web NFC is unavailable.
"""

from __future__ import annotations

import argparse
import json
import os
import sys
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from typing import Any, Protocol


DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 8787
DEFAULT_READER_FILTER = os.environ.get("NFC_READER_FILTER", "ACS")


class NfcProxyError(Exception):
    """Expected NFC proxy error returned to the browser."""


class RetryableNfcProxyError(NfcProxyError):
    """Expected transient error while a loop waits for the next tag."""


class PcscNoCard(Exception):
    """Raised while waiting for a tag to enter the reader field."""


class PcscConnection(Protocol):
    def transmit(self, apdu: list[int]) -> tuple[list[int], int, int]:
        ...

    def get_atr(self) -> list[int]:
        ...

    def disconnect(self) -> None:
        ...


class PcscBackend(Protocol):
    def list_readers(self) -> list[str]:
        ...

    def connect(self, reader_name: str) -> PcscConnection:
        ...


def ndef_text_message(text: str, language: str = "en") -> bytes:
    lang = language.encode("ascii")
    body = bytes([len(lang)]) + lang + text.encode("utf-8")
    if len(body) > 255:
        raise NfcProxyError("Payload is too large for a short NDEF text record.")

    return bytes(
        [
            0xD1,  # MB + ME + SR + TNF Well Known
            0x01,  # type length
            len(body),
            0x54,  # type "T"
        ]
    ) + body


def ndef_tlv(message: bytes, pad_to: int = 4) -> bytes:
    if len(message) < 0xFF:
        data = bytes([0x03, len(message)]) + message + bytes([0xFE])
    else:
        data = bytes([0x03, 0xFF, (len(message) >> 8) & 0xFF, len(message) & 0xFF]) + message + bytes([0xFE])

    padding = (-len(data)) % pad_to
    return data + (bytes(padding) if padding else b"")


class WinSCardConnection:
    SCARD_LEAVE_CARD = 0

    def __init__(self, dll: Any, card: Any, protocol: int, pci_t0: Any, pci_t1: Any, atr: list[int] | None = None):
        self.dll = dll
        self.card = card
        self.protocol = protocol
        self.pci_t0 = pci_t0
        self.pci_t1 = pci_t1
        self.atr = atr or []

    def transmit(self, apdu: list[int]) -> tuple[list[int], int, int]:
        import ctypes
        from ctypes import wintypes

        send = (ctypes.c_ubyte * len(apdu))(*apdu)
        recv = (ctypes.c_ubyte * 258)()
        recv_len = wintypes.DWORD(len(recv))
        pci = self.pci_t0 if self.protocol == WinSCardBackend.SCARD_PROTOCOL_T0 else self.pci_t1

        rv = self.dll.SCardTransmit(
            self.card,
            ctypes.byref(pci),
            send,
            len(apdu),
            None,
            recv,
            ctypes.byref(recv_len),
        )
        if pcsc_code(rv) in (WinSCardBackend.SCARD_E_NO_SMARTCARD, WinSCardBackend.SCARD_W_REMOVED_CARD):
            raise RetryableNfcProxyError(f"PC/SC transmit failed: {pcsc_error(rv)}")
        if pcsc_code(rv) != 0:
            raise NfcProxyError(f"PC/SC transmit failed: {pcsc_error(rv)}")

        response = list(recv[: recv_len.value])
        if len(response) < 2:
            raise NfcProxyError("Reader returned a malformed APDU response.")
        return response[:-2], response[-2], response[-1]

    def disconnect(self) -> None:
        self.dll.SCardDisconnect(self.card, self.SCARD_LEAVE_CARD)

    def get_atr(self) -> list[int]:
        return list(self.atr)


class WinSCardBackend:
    SCARD_SCOPE_USER = 0
    SCARD_SHARE_SHARED = 2
    SCARD_PROTOCOL_T0 = 1
    SCARD_PROTOCOL_T1 = 2
    SCARD_E_NO_SMARTCARD = 0x8010000C
    SCARD_E_NO_READERS_AVAILABLE = 0x8010002E
    SCARD_W_REMOVED_CARD = 0x80100069

    def __init__(self):
        if os.name != "nt":
            raise NfcProxyError("WinSCard backend is only available on Windows.")

        import ctypes
        from ctypes import wintypes

        self.ctypes = ctypes
        self.wintypes = wintypes
        self.dll = ctypes.WinDLL("winscard")
        self.context = ctypes.c_void_p()

        self.dll.SCardEstablishContext.argtypes = [
            wintypes.DWORD,
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.POINTER(ctypes.c_void_p),
        ]
        self.dll.SCardListReadersW.argtypes = [
            ctypes.c_void_p,
            wintypes.LPCWSTR,
            wintypes.LPWSTR,
            ctypes.POINTER(wintypes.DWORD),
        ]
        self.dll.SCardConnectW.argtypes = [
            ctypes.c_void_p,
            wintypes.LPCWSTR,
            wintypes.DWORD,
            wintypes.DWORD,
            ctypes.POINTER(ctypes.c_void_p),
            ctypes.POINTER(wintypes.DWORD),
        ]
        self.dll.SCardTransmit.argtypes = [
            ctypes.c_void_p,
            ctypes.c_void_p,
            ctypes.POINTER(ctypes.c_ubyte),
            wintypes.DWORD,
            ctypes.c_void_p,
            ctypes.POINTER(ctypes.c_ubyte),
            ctypes.POINTER(wintypes.DWORD),
        ]
        self.dll.SCardDisconnect.argtypes = [ctypes.c_void_p, wintypes.DWORD]
        self.dll.SCardStatusW.argtypes = [
            ctypes.c_void_p,
            wintypes.LPWSTR,
            ctypes.POINTER(wintypes.DWORD),
            ctypes.POINTER(wintypes.DWORD),
            ctypes.POINTER(wintypes.DWORD),
            ctypes.POINTER(ctypes.c_ubyte),
            ctypes.POINTER(wintypes.DWORD),
        ]

        class ScardIoRequest(ctypes.Structure):
            _fields_ = [("dwProtocol", wintypes.DWORD), ("cbPciLength", wintypes.DWORD)]

        self.pci_t0 = ScardIoRequest.in_dll(self.dll, "g_rgSCardT0Pci")
        self.pci_t1 = ScardIoRequest.in_dll(self.dll, "g_rgSCardT1Pci")

        rv = self.dll.SCardEstablishContext(self.SCARD_SCOPE_USER, None, None, self.ctypes.byref(self.context))
        if pcsc_code(rv) != 0:
            raise NfcProxyError(f"Could not open Windows PC/SC context: {pcsc_error(rv)}")

    def list_readers(self) -> list[str]:
        size = self.wintypes.DWORD(0)
        rv = self.dll.SCardListReadersW(self.context, None, None, self.ctypes.byref(size))
        if pcsc_code(rv) == self.SCARD_E_NO_READERS_AVAILABLE:
            return []
        if pcsc_code(rv) != 0:
            raise NfcProxyError(f"Could not list PC/SC readers: {pcsc_error(rv)}")

        buffer = self.ctypes.create_unicode_buffer(size.value)
        rv = self.dll.SCardListReadersW(self.context, None, buffer, self.ctypes.byref(size))
        if pcsc_code(rv) != 0:
            raise NfcProxyError(f"Could not list PC/SC readers: {pcsc_error(rv)}")

        raw = buffer[: size.value]
        return [name for name in raw.split("\x00") if name]

    def connect(self, reader_name: str) -> PcscConnection:
        card = self.ctypes.c_void_p()
        protocol = self.wintypes.DWORD(0)
        rv = self.dll.SCardConnectW(
            self.context,
            reader_name,
            self.SCARD_SHARE_SHARED,
            self.SCARD_PROTOCOL_T0 | self.SCARD_PROTOCOL_T1,
            self.ctypes.byref(card),
            self.ctypes.byref(protocol),
        )
        if pcsc_code(rv) in (self.SCARD_E_NO_SMARTCARD, self.SCARD_W_REMOVED_CARD):
            raise PcscNoCard()
        if pcsc_code(rv) != 0:
            raise NfcProxyError(f"Could not connect to '{reader_name}': {pcsc_error(rv)}")

        return WinSCardConnection(self.dll, card, protocol.value, self.pci_t0, self.pci_t1, self._card_atr(card))

    def _card_atr(self, card: Any) -> list[int]:
        atr = (self.ctypes.c_ubyte * 64)()
        atr_len = self.wintypes.DWORD(len(atr))
        state = self.wintypes.DWORD(0)
        protocol = self.wintypes.DWORD(0)
        rv = self.dll.SCardStatusW(card, None, None, self.ctypes.byref(state), self.ctypes.byref(protocol), atr, self.ctypes.byref(atr_len))
        if pcsc_code(rv) != 0:
            return []
        return list(atr[: atr_len.value])


class PyscardConnection:
    def __init__(self, connection: Any):
        self.connection = connection

    def transmit(self, apdu: list[int]) -> tuple[list[int], int, int]:
        data, sw1, sw2 = self.connection.transmit(apdu)
        return list(data), sw1, sw2

    def get_atr(self) -> list[int]:
        try:
            return list(self.connection.getATR())
        except Exception:
            return []

    def disconnect(self) -> None:
        try:
            self.connection.disconnect()
        except Exception:
            pass


class PyscardBackend:
    def __init__(self):
        try:
            from smartcard.CardConnection import CardConnection
            from smartcard.Exceptions import CardConnectionException, NoCardException
            from smartcard.System import readers
        except ImportError as exc:
            raise NfcProxyError("Python package 'pyscard' is not installed. Run: py -m pip install pyscard") from exc

        self.card_connection = CardConnection
        self.card_connection_exception = CardConnectionException
        self.no_card_exception = NoCardException
        self.readers_func = readers

    def list_readers(self) -> list[str]:
        return [str(reader) for reader in self.readers_func()]

    def connect(self, reader_name: str) -> PcscConnection:
        pcsc_readers = list(self.readers_func())
        names = [str(reader) for reader in pcsc_readers]
        if reader_name not in names:
            raise NfcProxyError(f"Reader disappeared: {reader_name}")

        connection = pcsc_readers[names.index(reader_name)].createConnection()
        try:
            connection.connect(self.card_connection.T0_protocol | self.card_connection.T1_protocol)
        except self.no_card_exception:
            raise PcscNoCard()
        except self.card_connection_exception as exc:
            raise NfcProxyError(str(exc)) from exc

        return PyscardConnection(connection)


def pcsc_code(value: int) -> int:
    return value & 0xFFFFFFFF


def pcsc_error(value: int) -> str:
    code = pcsc_code(value)
    names = {
        0x8010000C: "SCARD_E_NO_SMARTCARD",
        0x8010001D: "SCARD_E_NO_SERVICE",
        0x8010002E: "SCARD_E_NO_READERS_AVAILABLE",
        0x80100069: "SCARD_W_REMOVED_CARD",
    }
    suffix = f" ({names[code]})" if code in names else ""
    return f"0x{code:08X}{suffix}"


class PcscType2Writer:
    def __init__(self, reader_filter: str | None = DEFAULT_READER_FILTER):
        self.reader_filter = reader_filter or ""
        self._backend: PcscBackend | None = None

    def _load_backend(self) -> PcscBackend:
        if self._backend is not None:
            return self._backend

        if os.name == "nt":
            try:
                self._backend = WinSCardBackend()
                return self._backend
            except NfcProxyError as exc:
                raise NfcProxyError(str(exc)) from exc

        try:
            self._backend = PyscardBackend()
            return self._backend
        except NfcProxyError as exc:
            raise NfcProxyError(str(exc)) from exc

    def list_readers(self) -> list[str]:
        return self._load_backend().list_readers()

    def status(self) -> dict[str, Any]:
        try:
            names = self.list_readers()
            selected = self._select_reader_name(names)
            return {
                "ok": True,
                "pcsc": True,
                "reader": selected,
                "readers": names,
                "writer": "pcsc",
                "message": "ACS proxy ready" if selected else "No PC/SC reader found",
            }
        except NfcProxyError as exc:
            return {
                "ok": False,
                "pcsc": False,
                "reader": None,
                "readers": [],
                "writer": "pcsc",
                "message": str(exc),
            }

    def write_text(self, text: str, timeout_s: float = 15.0, ignore_uid: str | None = None) -> dict[str, Any]:
        deadline = time.monotonic() + timeout_s
        ignore_uid = ignore_uid.upper() if ignore_uid else None

        while True:
            remaining_s = max(0.1, deadline - time.monotonic())
            connection, reader_name = self._wait_for_card(remaining_s)
            uid = self._try_get_uid(connection)

            if ignore_uid and uid and uid.upper() == ignore_uid:
                connection.disconnect()
                if time.monotonic() >= deadline:
                    raise RetryableNfcProxyError("Waiting for a different NFC tag. Remove the current tag or present the next one.")
                time.sleep(0.2)
                continue

            break

        try:
            try:
                tag_type = self._detect_tag_type(connection)
                if tag_type == "type5":
                    result = self._write_type5_text(connection, text)
                    bytes_written = result["bytes_written"]
                else:
                    cc = self._read_page(connection, 3)
                    result = self._write_type2_text(connection, text, cc)
                    tag_type = "type2"
                    bytes_written = result["bytes_written"]
            except RetryableNfcProxyError:
                raise
            except NfcProxyError:
                result = self._write_type5_text(connection, text)
                tag_type = "type5"
                bytes_written = result["bytes_written"]

            return {
                "ok": True,
                "reader": reader_name,
                "uid": uid,
                "bytes_written": bytes_written,
                "tag_type": tag_type,
                "writer": "pcsc",
            }
        finally:
            connection.disconnect()

    def _detect_tag_type(self, connection: PcscConnection) -> str:
        atr = connection.get_atr()
        if len(atr) > 12:
            if atr[12] == 0x03:
                return "type2"
            if atr[12] == 0x0B:
                return "type5"

        if len(atr) > 14:
            if atr[14] == 0x03:
                return "type2"
            if atr[14] == 0x0B:
                return "type5"

        return "unknown"

    def _select_reader_name(self, names: list[str]) -> str | None:
        if not names:
            return None

        if self.reader_filter:
            needle = self.reader_filter.lower()
            for name in names:
                if needle in name.lower():
                    return name

        for name in names:
            if "acs" in name.lower():
                return name

        return names[0]

    def _wait_for_card(self, timeout_s: float) -> tuple[PcscConnection, str]:
        backend = self._load_backend()
        deadline = time.monotonic() + timeout_s
        last_error = "No NFC tag detected."

        while time.monotonic() <= deadline:
            names = backend.list_readers()
            selected_name = self._select_reader_name(names)
            if selected_name is None:
                raise NfcProxyError("No PC/SC reader found. Is the ACS reader connected?")

            try:
                return backend.connect(selected_name), selected_name
            except PcscNoCard:
                last_error = "No NFC tag detected. Hold a tag on the reader."
            except NfcProxyError as exc:
                last_error = str(exc)

            time.sleep(0.2)

        raise RetryableNfcProxyError(last_error)

    def _transmit(self, connection: PcscConnection, apdu: list[int], label: str) -> list[int]:
        data, sw1, sw2 = connection.transmit(apdu)
        if (sw1, sw2) != (0x90, 0x00):
            if label.startswith("ISO15693") and (sw1, sw2) in ((0x64, 0x01), (0x63, 0x00)):
                raise RetryableNfcProxyError(f"{label} failed with status {sw1:02X} {sw2:02X}.")
            raise NfcProxyError(f"{label} failed with status {sw1:02X} {sw2:02X}.")
        return list(data)

    def _try_get_uid(self, connection: PcscConnection) -> str | None:
        try:
            data = self._transmit(connection, [0xFF, 0xCA, 0x00, 0x00, 0x00], "Get UID")
            return "".join(f"{value:02X}" for value in data)
        except NfcProxyError:
            return None

    def _read_page(self, connection: PcscConnection, page: int) -> bytes:
        data = self._transmit(connection, [0xFF, 0xB0, 0x00, page & 0xFF, 0x04], f"Read page {page}")
        if len(data) != 4:
            raise NfcProxyError(f"Read page {page} returned {len(data)} bytes instead of 4.")
        return bytes(data)

    def _write_page(self, connection: PcscConnection, page: int, data: bytes) -> None:
        if len(data) != 4:
            raise ValueError("Type 2 tag pages are exactly 4 bytes.")
        self._transmit(connection, [0xFF, 0xD6, 0x00, page & 0xFF, 0x04, *data], f"Write page {page}")

    def _type2_capacity(self, cc: bytes) -> int | None:
        if len(cc) == 4 and cc[0] == 0xE1:
            return cc[2] * 8
        return None

    def _write_type2_text(self, connection: PcscConnection, text: str, cc: bytes) -> dict[str, Any]:
        capacity = self._type2_capacity(cc)
        data = ndef_tlv(ndef_text_message(text))

        if capacity is not None and len(data) > capacity:
            raise NfcProxyError(f"Payload needs {len(data)} bytes, but this Type 2 tag reports {capacity} writable bytes.")

        page = 4
        for offset in range(0, len(data), 4):
            self._write_page(connection, page + (offset // 4), data[offset : offset + 4])

        return {"bytes_written": len(data)}

    def _write_type5_text(self, connection: PcscConnection, text: str) -> dict[str, Any]:
        info = self._type5_system_info(connection)
        block_size = info["block_size"]
        block_count = info["block_count"]
        total_bytes = block_size * block_count
        layout = self._type5_layout(connection, block_size, total_bytes)
        print(
            f"Type 5 layout: block_size={block_size}, blocks={block_count}, "
            f"formatted={layout['formatted']}, first_ndef_block={layout['first_ndef_block']}"
        )
        data = ndef_tlv(ndef_text_message(text), pad_to=block_size)
        data_capacity = total_bytes - (layout["first_ndef_block"] * block_size)

        if len(data) > data_capacity:
            raise NfcProxyError(f"Payload needs {len(data)} bytes, but this Type 5 tag reports {data_capacity} NDEF bytes.")

        bytes_written = 0
        cc_data = layout["cc"]
        cc_data += bytes((-len(cc_data)) % block_size)
        for offset in range(0, len(cc_data), block_size):
            self._type5_write_block(connection, offset // block_size, cc_data[offset : offset + block_size])
        bytes_written += len(cc_data)

        for offset in range(0, len(data), block_size):
            block = layout["first_ndef_block"] + (offset // block_size)
            self._type5_write_block(connection, block, data[offset : offset + block_size])
            bytes_written += block_size

        return {"bytes_written": bytes_written}

    def _type5_layout(self, connection: PcscConnection, block_size: int, total_bytes: int) -> dict[str, Any]:
        block0 = self._type5_read_block(connection, 0, block_size)
        magic = block0[0] if block0 else 0x00

        if magic == 0xE1:
            return {
                "formatted": True,
                "cc": bytes(block0[:4]),
                "first_ndef_block": 1,
            }

        if magic == 0xE2:
            cc_blocks = max(1, (8 + block_size - 1) // block_size)
            cc = bytearray()
            for block in range(cc_blocks):
                cc.extend(self._type5_read_block(connection, block, block_size))
            return {
                "formatted": True,
                "cc": bytes(cc[:8]),
                "first_ndef_block": cc_blocks,
            }

        return {
            "formatted": False,
            "cc": self._type5_capability_container(total_bytes),
            "first_ndef_block": 1,
        }

    def _type5_system_info(self, connection: PcscConnection) -> dict[str, int]:
        data = self._transmit(connection, [0xFF, 0xFB, 0x00, 0x00, 0x01, 0x2B], "ISO15693 Get System Information")
        if len(data) < 9:
            raise NfcProxyError("ISO15693 Get System Information returned too few bytes.")

        flags = data[0]
        offset = 1 + 8
        if flags & 0x01:
            offset += 1
        if flags & 0x02:
            offset += 1

        if flags & 0x04:
            if len(data) < offset + 2:
                raise NfcProxyError("ISO15693 system information did not include complete memory size.")
            block_count = data[offset] + 1
            block_size = data[offset + 1] + 1
        else:
            raise NfcProxyError("ISO15693 tag did not report memory size.")

        if block_size <= 0 or block_size > 32 or block_count <= 0:
            raise NfcProxyError(f"Unsupported ISO15693 memory layout: {block_count} blocks of {block_size} bytes.")

        return {"block_count": block_count, "block_size": block_size}

    def _type5_read_block(self, connection: PcscConnection, block: int, block_size: int) -> bytes:
        data = self._transmit(connection, self._type5_block_apdu(0x20, 0x30, block), f"ISO15693 Read Single Block {block}")
        if len(data) != block_size:
            raise NfcProxyError(f"ISO15693 block {block} returned {len(data)} bytes instead of {block_size}.")
        return bytes(data)

    def _type5_write_block(self, connection: PcscConnection, block: int, data: bytes) -> None:
        if len(data) == 4 and block <= 0xFF:
            self._write_page(connection, block, data)
            return

        command = 0x21 if block <= 0xFF else 0x32
        block_ref = [block & 0xFF] if command == 0x21 else [block & 0xFF, (block >> 8) & 0xFF]
        apdu = [0xFF, 0xFB, 0x00, 0x00, 1 + len(block_ref) + len(data), command, *block_ref, *data]
        self._transmit(connection, apdu, f"ISO15693 Write Single Block {block}")

    def _type5_block_apdu(self, command: int, extended_command: int, block: int) -> list[int]:
        if block <= 0xFF:
            return [0xFF, 0xFB, 0x00, 0x00, 0x02, command, block & 0xFF]
        return [0xFF, 0xFB, 0x00, 0x00, 0x03, extended_command, block & 0xFF, (block >> 8) & 0xFF]

    def _type5_capability_container(self, total_bytes: int) -> bytes:
        if total_bytes > 2040:
            raise NfcProxyError("Unformatted Type 5 tags above 2040 bytes are not supported yet.")

        return bytes([0xE1, 0x40, max(1, total_bytes // 8), 0x01])


class ProxyHandler(BaseHTTPRequestHandler):
    writer: PcscType2Writer

    def do_OPTIONS(self) -> None:
        self._send_json({"ok": True})

    def do_GET(self) -> None:
        if self.path.split("?", 1)[0] == "/status":
            self._send_json(self.writer.status())
            return

        self._send_json({"ok": False, "message": "Use /status or /write."}, status=404)

    def do_POST(self) -> None:
        if self.path.split("?", 1)[0] != "/write":
            self._send_json({"ok": False, "message": "Use /write."}, status=404)
            return

        try:
            request = self._read_request_json()
            text = request.get("json")
            if not isinstance(text, str) or not text:
                raise NfcProxyError("Request must include a non-empty 'json' string.")

            timeout_s = float(request.get("timeout_s", 15.0))
            ignore_uid = request.get("ignore_uid")
            if ignore_uid is not None and not isinstance(ignore_uid, str):
                raise NfcProxyError("'ignore_uid' must be a string when provided.")

            result = self.writer.write_text(text, timeout_s=timeout_s, ignore_uid=ignore_uid)
            if not isinstance(result, dict):
                raise NfcProxyError("Writer returned no result.")

            print(f"Wrote {result.get('tag_type', 'unknown')} tag: {result.get('bytes_written', 0)} bytes")
            self._send_json(result)
        except Exception as exc:
            message = str(exc) or exc.__class__.__name__
            print(f"Write failed: {message}")
            self._send_json({"ok": False, "message": message, "retryable": isinstance(exc, RetryableNfcProxyError)}, status=400)

    def log_message(self, fmt: str, *args: Any) -> None:
        print("%s - %s" % (self.address_string(), fmt % args))

    def _read_request_json(self) -> dict[str, Any]:
        length = int(self.headers.get("Content-Length", "0"))
        if length <= 0 or length > 8192:
            raise NfcProxyError("Invalid request body length.")

        body = self.rfile.read(length)
        try:
            value = json.loads(body.decode("utf-8"))
        except json.JSONDecodeError as exc:
            raise NfcProxyError("Request body must be JSON.") from exc

        if not isinstance(value, dict):
            raise NfcProxyError("Request body must be a JSON object.")
        return value

    def _send_json(self, value: dict[str, Any], status: int = 200) -> None:
        body = json.dumps(value).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")
        self.send_header("Cache-Control", "no-store")
        self.end_headers()
        self.wfile.write(body)


def main() -> None:
    parser = argparse.ArgumentParser(description="Local PC/SC NFC writer proxy for nfc_writer.html")
    parser.add_argument("--host", default=DEFAULT_HOST)
    parser.add_argument("--port", type=int, default=DEFAULT_PORT)
    parser.add_argument("--reader-filter", default=DEFAULT_READER_FILTER)
    args = parser.parse_args()

    ProxyHandler.writer = PcscType2Writer(args.reader_filter)
    server = ThreadingHTTPServer((args.host, args.port), ProxyHandler)

    print(f"NFC proxy listening on http://{args.host}:{args.port}")
    print("Open nfc_writer.html in the browser, then hold a Type 2 NFC tag on the ACS reader when writing.")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nStopping NFC proxy.")
        sys.exit(0)


if __name__ == "__main__":
    main()
