"""System and firmware tools — identification, motor dynamics, A3981, NVS."""

from fastmcp import Context

from mcbirdcage.state import BirdcageState


def _require_device(state: BirdcageState):
    if state.device is None:
        raise RuntimeError("No device connected. Use the 'connect' tool first.")


def register(mcp):
    """Register system tools on the FastMCP instance."""

    @mcp.tool()
    async def get_firmware_id(ctx: Context) -> dict:
        """Read full MCU and firmware identification.

        Returns NVS version, system ID, silicon info, board ID, antenna
        ID, software version, clock frequencies, and flash layout.
        """
        state: BirdcageState = ctx.request_context.lifespan_context
        _require_device(state)
        raw = state.device.get_firmware_id()
        return {"raw": raw}

    @mcp.tool()
    async def get_motor_dynamics(ctx: Context) -> dict:
        """Read max velocity and acceleration for both motor axes.

        Returns {"az_max_vel", "el_max_vel", "az_accel", "el_accel"}
        in degrees/sec and degrees/sec^2 respectively.
        """
        state: BirdcageState = ctx.request_context.lifespan_context
        _require_device(state)
        return state.device.get_motor_dynamics()

    @mcp.tool()
    async def set_max_velocity(
        ctx: Context,
        motor_id: int,
        deg_per_sec: float,
    ) -> dict:
        """Set the maximum velocity for a motor axis.

        Args:
            motor_id: 0 for azimuth, 1 for elevation.
            deg_per_sec: Maximum velocity in degrees per second.
                         Carryout G2 defaults: AZ=65, EL=45.
        """
        state: BirdcageState = ctx.request_context.lifespan_context
        _require_device(state)
        if motor_id not in (0, 1):
            raise ValueError("motor_id must be 0 (azimuth) or 1 (elevation)")
        state.device.set_max_velocity(motor_id, deg_per_sec)
        axis = "azimuth" if motor_id == 0 else "elevation"
        return {"status": "set", "axis": axis, "max_velocity": deg_per_sec}

    @mcp.tool()
    async def set_max_acceleration(
        ctx: Context,
        motor_id: int,
        accel: float,
    ) -> dict:
        """Set the maximum acceleration for a motor axis.

        Args:
            motor_id: 0 for azimuth, 1 for elevation.
            accel: Maximum acceleration in degrees/sec^2.
                   Carryout G2 default: 400 for both axes.
        """
        state: BirdcageState = ctx.request_context.lifespan_context
        _require_device(state)
        if motor_id not in (0, 1):
            raise ValueError("motor_id must be 0 (azimuth) or 1 (elevation)")
        state.device.set_max_acceleration(motor_id, accel)
        axis = "azimuth" if motor_id == 0 else "elevation"
        return {"status": "set", "axis": axis, "max_acceleration": accel}

    @mcp.tool()
    async def get_motor_life(ctx: Context) -> dict:
        """Read motor lifetime and usage statistics.

        Returns total moves, total degrees traveled, and uptime hours
        for both AZ and EL axes.
        """
        state: BirdcageState = ctx.request_context.lifespan_context
        _require_device(state)
        raw = state.device.get_motor_life()
        return {"raw": raw}

    @mcp.tool()
    async def get_pid_gains(ctx: Context) -> dict:
        """Read PID controller gains for both motor axes.

        Returns {"az": {"kp", "kv", "ki"}, "el": {"kp", "kv", "ki"}}.
        Carryout G2 defaults: AZ Kp=600/Kv=60/Ki=1, EL Kp=250/Kv=50/Ki=1.
        """
        state: BirdcageState = ctx.request_context.lifespan_context
        _require_device(state)
        return state.device.get_pid_gains()

    @mcp.tool()
    async def set_pid_gains(
        ctx: Context,
        motor_id: int,
        kp: float,
        kv: float,
        ki: float,
    ) -> dict:
        """Set PID controller gains for a motor axis.

        CAUTION: Incorrect PID values can cause oscillation or motor damage.

        Args:
            motor_id: 0 for azimuth, 1 for elevation.
            kp: Proportional gain.
            kv: Velocity (derivative) gain.
            ki: Integral gain.
        """
        state: BirdcageState = ctx.request_context.lifespan_context
        _require_device(state)
        if motor_id not in (0, 1):
            raise ValueError("motor_id must be 0 (azimuth) or 1 (elevation)")
        state.device.set_pid_gains(motor_id, kp, kv, ki)
        axis = "azimuth" if motor_id == 0 else "elevation"
        return {"status": "set", "axis": axis, "kp": kp, "kv": kv, "ki": ki}

    @mcp.tool()
    async def get_a3981_diag(ctx: Context) -> dict:
        """Read Allegro A3981 stepper driver diagnostic status.

        Returns fault status for both AZ and EL motor drivers.
        Normal response is "AZ DIAG: OK / EL DIAG: OK".
        """
        state: BirdcageState = ctx.request_context.lifespan_context
        _require_device(state)
        raw = state.device.get_a3981_diag()
        return {"raw": raw}

    @mcp.tool()
    async def get_a3981_modes(ctx: Context) -> dict:
        """Read A3981 stepper driver operating modes.

        Returns step mode (AUTO/fixed), current mode (AUTO/HiZ/LoZ),
        and step size (FULL=16 through SIXTEENTH=1) for both drivers.
        """
        state: BirdcageState = ctx.request_context.lifespan_context
        _require_device(state)
        return state.device.get_a3981_modes()

    @mcp.tool()
    async def nvs_dump(ctx: Context) -> dict:
        """Dump all non-volatile storage (NVS) values.

        Returns the complete NVS table with index, name, current value,
        saved value, and default value for every setting.
        """
        state: BirdcageState = ctx.request_context.lifespan_context
        _require_device(state)
        raw = state.device.nvs_dump()
        return {"raw": raw}

    @mcp.tool()
    async def nvs_read(ctx: Context, index: int) -> dict:
        """Read a single NVS value by index.

        Key indices: 20=Disable Tracker, 80=AZ Max Vel, 85=EL Max Vel,
        101=Min EL, 102=Max EL, 128-133=PID gains.

        Args:
            index: NVS index number (0-143).
        """
        state: BirdcageState = ctx.request_context.lifespan_context
        _require_device(state)
        raw = state.device.nvs_read(index)
        return {"index": index, "raw": raw}
