"""MIFARE Classic commands: hf.mf.*"""
import struct
from .protocol import Cmd
from .transport import PM3Transport, PM3Error

MF_KEY_A = 0
MF_KEY_B = 1
MFBLOCK_SIZE = 16


class HFMFCommands:
    """MIFARE Classic commands."""

    def __init__(self, transport: PM3Transport):
        self._t = transport

    def _parse_key(self, key: str | bytes) -> bytes:
        if isinstance(key, str):
            key = bytes.fromhex(key)
        if len(key) != 6:
            raise ValueError("Key must be 6 bytes")
        return key

    async def rdbl(self, block: int, key: str | bytes = "FFFFFFFFFFFF",
                   key_type: int = MF_KEY_A) -> dict:
        """Read a MIFARE Classic block."""
        key_bytes = self._parse_key(key)
        payload = struct.pack("<BB", block, key_type) + key_bytes
        resp = await self._t.send_ng(Cmd.HF_MIFARE_READBL, payload, timeout=5.0)

        if resp.status != 0:
            return {"success": False, "error": resp.status}

        return {
            "success": True,
            "block": block,
            "data": resp.data[:MFBLOCK_SIZE].hex(),
            "raw": resp.data[:MFBLOCK_SIZE],
        }

    async def wrbl(self, block: int, data: str | bytes,
                   key: str | bytes = "FFFFFFFFFFFF",
                   key_type: int = MF_KEY_A) -> dict:
        """Write a MIFARE Classic block."""
        key_bytes = self._parse_key(key)
        if isinstance(data, str):
            data = bytes.fromhex(data)
        if len(data) != MFBLOCK_SIZE:
            raise ValueError(f"Block data must be {MFBLOCK_SIZE} bytes")

        # MIX format: arg0=blockno, arg1=keytype
        # payload = key(6) + reserved(10) + data(16) = 32 bytes
        payload = key_bytes + b"\x00" * 10 + data
        resp = await self._t.send_mix(
            Cmd.HF_MIFARE_WRITEBL, arg0=block, arg1=key_type,
            payload=payload, timeout=5.0,
        )

        success = resp.oldarg[0] > 0 if resp.oldarg else resp.status == 0
        return {"success": success, "block": block}

    async def rdsc(self, sector: int, key: str | bytes = "FFFFFFFFFFFF",
                   key_type: int = MF_KEY_A) -> dict:
        """Read an entire MIFARE Classic sector."""
        # Sectors 0-31 have 4 blocks, sectors 32-39 have 16 blocks
        if sector < 32:
            first_block = sector * 4
            num_blocks = 4
        else:
            first_block = 128 + (sector - 32) * 16
            num_blocks = 16

        blocks = {}
        for i in range(num_blocks):
            block_num = first_block + i
            result = await self.rdbl(block_num, key=key, key_type=key_type)
            blocks[block_num] = result

        return {
            "sector": sector,
            "first_block": first_block,
            "num_blocks": num_blocks,
            "blocks": blocks,
        }

    async def chk(self, block: int, keys: list[str | bytes],
                  key_type: int = MF_KEY_A) -> dict:
        """Check which key works for a block."""
        for key in keys:
            try:
                result = await self.rdbl(block, key=key, key_type=key_type)
                if result.get("success"):
                    key_hex = key if isinstance(key, str) else key.hex()
                    return {"found": True, "key": key_hex, "key_type": key_type}
            except PM3Error:
                continue
        return {"found": False}

    async def sniff(self) -> dict:
        """Sniff MIFARE traffic."""
        resp = await self._t.send_ng(Cmd.HF_MIFARE_SNIFF, timeout=30.0)
        return {"status": resp.status}

    async def sim(self, uid: str | bytes, size: str = "1k") -> dict:
        """Simulate MIFARE Classic card.
        size: "mini", "1k", "2k", "4k"
        """
        if isinstance(uid, str):
            uid = bytes.fromhex(uid)

        size_flags = {"mini": 0x0000, "1k": 0x0040, "2k": 0x0080, "4k": 0x00C0}
        if size not in size_flags:
            raise ValueError(f"Unknown size: {size}")

        uid_flag = {4: 0x0010, 7: 0x0020, 10: 0x0030}.get(len(uid))
        if uid_flag is None:
            raise ValueError("UID must be 4, 7, or 10 bytes")

        flags = 0x0001 | uid_flag | size_flags[size]  # FLAG_INTERACTIVE
        resp = await self._t.send_mix(
            Cmd.HF_MIFARE_SIMULATE, arg0=flags, payload=uid, timeout=60.0,
        )
        return {"status": resp.status}

    async def nested(self, block: int, key: str | bytes = "FFFFFFFFFFFF",
                     key_type: int = MF_KEY_A,
                     target_block: int = 0, target_key_type: int = MF_KEY_A) -> dict:
        """Run nested attack to recover keys."""
        key_bytes = self._parse_key(key)
        payload = struct.pack("<BB", block, key_type) + key_bytes
        payload += struct.pack("<BB", target_block, target_key_type)
        resp = await self._t.send_ng(Cmd.HF_MIFARE_NESTED, payload, timeout=30.0)
        return {"status": resp.status, "data": resp.data.hex()}

    async def cident(self) -> dict:
        """Identify magic (gen1/gen2) card type."""
        resp = await self._t.send_ng(Cmd.HF_MIFARE_CIDENT, timeout=5.0)
        return {"status": resp.status, "data": resp.data.hex() if resp.data else None}
