"""Hardware commands: hw.*"""
import struct
import asyncio
from .protocol import Cmd, PM3Status
from .transport import PM3Transport, PM3Response, PM3Error, ProgressCallback


class HWCommands:
    """Proxmark3 hardware commands."""

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

    async def ping(self, length: int = 32) -> dict:
        """Send ping and verify echo. Returns {success, length, data}."""
        data = bytes(i & 0xFF for i in range(length))
        resp = await self._t.send_ng(Cmd.PING, data)
        match = resp.data == data
        return {"success": match, "length": len(resp.data), "data": resp.data.hex()}

    async def version(self) -> dict:
        """Get firmware version. Returns {chip_id, section_size, version_string}."""
        resp = await self._t.send_ng(Cmd.VERSION, timeout=3.0)
        if len(resp.data) < 12:
            raise PM3Error("Version response too short")
        chip_id, section_size, vstr_len = struct.unpack_from("<III", resp.data, 0)
        vstr = resp.data[12:12 + vstr_len].decode("utf-8", errors="replace").rstrip("\x00")
        return {
            "chip_id": f"0x{chip_id:08X}",
            "section_size": section_size,
            "version_string": vstr,
        }

    async def status(self, speed_test_timeout: int = 0) -> dict:
        """Get device status. Returns {status}."""
        if speed_test_timeout > 0:
            payload = struct.pack("<i", speed_test_timeout)
        else:
            payload = b""
        resp = await self._t.send_ng(Cmd.STATUS, payload, timeout=2.0 + speed_test_timeout / 1000)
        return {"status": resp.status}

    async def led(self, led: int, action: str = "on", brightness: int = 100,
                  speed: int = 500, count: int = 5) -> dict:
        """Control LEDs. led: bitmask (1=A,2=B,4=C,8=D). action: on/off/toggle/pwm/pulse/fade/blink."""
        actions = {"off": 0, "on": 1, "toggle": 2, "pwm": 3, "pulse": 4, "fade": 5, "blink": 6}
        if action not in actions:
            raise ValueError(f"Unknown action: {action}. Use one of: {list(actions.keys())}")
        payload = struct.pack("<BBBHH", led, actions[action], brightness, speed, count)
        resp = await self._t.send_ng(Cmd.LED_CONTROL, payload)
        return {"status": resp.status}

    async def dbg(self, level: int | None = None) -> dict:
        """Get or set debug level. None=get, 0-4=set."""
        if level is None:
            resp = await self._t.send_ng(Cmd.GET_DBGMODE)
            if len(resp.data) >= 1:
                return {"level": resp.data[0]}
            return {"level": 0}
        else:
            payload = struct.pack("<B", level)
            resp = await self._t.send_ng(Cmd.SET_DBGMODE, payload)
            return {"level": level, "status": resp.status}

    async def tearoff(self, delay_us: int = 0, on: bool = True) -> dict:
        """Set tearoff parameters."""
        payload = struct.pack("<HbBB", delay_us, 0, int(on), int(not on))
        resp = await self._t.send_ng(Cmd.SET_TEAROFF, payload)
        return {"status": resp.status}

    async def break_loop(self) -> None:
        """Send break to stop long-running operations."""
        await self._t.send_ng_no_response(Cmd.BREAK_LOOP)

    async def standalone(self) -> dict:
        """Start standalone mode."""
        resp = await self._t.send_ng(Cmd.STANDALONE, timeout=5.0)
        return {"status": resp.status}

    async def reset(self) -> None:
        """Reset the device."""
        await self._t.send_ng_no_response(Cmd.HARDWARE_RESET)

    async def capabilities(self) -> dict:
        """Get device capabilities."""
        resp = await self._t.send_ng(Cmd.CAPABILITIES)
        if len(resp.data) < 9:
            raise PM3Error("Capabilities response too short")
        version = resp.data[0]
        baudrate = struct.unpack_from("<I", resp.data, 1)[0]
        bigbuf_size = struct.unpack_from("<I", resp.data, 5)[0]
        # Bit flags start at byte 9
        flags = 0
        if len(resp.data) > 9:
            flags = struct.unpack_from("<I", resp.data, 9)[0] if len(resp.data) >= 13 else resp.data[9]
        return {
            "version": version,
            "baudrate": baudrate,
            "bigbuf_size": bigbuf_size,
            "raw_flags": flags,
        }

    async def tune(self, on_progress: ProgressCallback = None) -> dict:
        """Run full antenna tuning. Returns LF/HF voltages, peak, Q factor."""
        resp = await self._t.send_ng(Cmd.MEASURE_ANTENNA_TUNING, timeout=20.0)
        if resp.status != 0:
            raise PM3Error("Antenna tuning failed", status=resp.status)
        if len(resp.data) < 28:
            raise PM3Error("Tune response too short")

        v_lf134, v_lf125, v_lfconf, v_hf, peak_v, peak_f, divisor = struct.unpack_from("<IIIIIIi", resp.data, 0)
        results = list(resp.data[28:28 + 256]) if len(resp.data) >= 284 else []

        ANTENNA_ERROR = 1.09
        LF_DIVISOR_125 = 95
        LF_DIVISOR_134 = 88

        def div2freq(d):
            return 12000.0 / (d + 1) if d > 0 else 0

        result = {
            "lf": {
                "125kHz_mV": v_lf125,
                "125kHz_V": round(v_lf125 * ANTENNA_ERROR / 1000, 2),
                "134kHz_mV": v_lf134,
                "134kHz_V": round(v_lf134 * ANTENNA_ERROR / 1000, 2),
                "conf_mV": v_lfconf,
                "peak_mV": peak_v,
                "peak_V": round(peak_v * ANTENNA_ERROR / 1000, 2),
                "peak_freq_kHz": round(div2freq(peak_f), 2),
                "peak_divisor": peak_f,
                "divisor": divisor,
            },
            "hf": {
                "13.56MHz_mV": v_hf,
                "13.56MHz_V": round(v_hf * ANTENNA_ERROR / 1000, 2),
            },
            "scan_results": results,
        }

        if on_progress:
            event = {"type": "tune_complete", "result": result}
            ret = on_progress(event)
            if asyncio.iscoroutine(ret):
                await ret

        return result
