"""SVD peripheral and register inspection tools."""

import logging
from pathlib import Path

from fastmcp import Context
from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool
from openocd.errors import OpenOCDError

from mcjtag.mixins.helpers import require_session
from mcjtag.models import (
    SVDFieldResult,
    SVDListResult,
    SVDPeripheralResult,
    SVDRegisterResult,
)
from mcjtag.state import SessionState

log = logging.getLogger(__name__)

# L3: Maximum SVD file size (50 MB — most SVD files are 1-10 MB)
_MAX_SVD_SIZE = 50 * 1024 * 1024


def _get_state(ctx: Context) -> SessionState:
    return ctx.request_context.lifespan_context


class SVDMixin(MCPMixin):
    """Tools for SVD-based peripheral register inspection and decoding."""

    @mcp_tool()
    async def svd_inspect(
        self,
        ctx: Context,
        svd_path: str | None = None,
        peripheral: str | None = None,
        register: str | None = None,
    ) -> SVDPeripheralResult | SVDListResult:
        """Inspect chip peripherals and registers using SVD (System View Description).

        SVD files describe a chip's memory-mapped peripherals and their
        register bitfields. This tool can:

        1. Load an SVD file (provide svd_path)
        2. List all peripherals (no peripheral specified)
        3. Read and decode all registers of a peripheral (provide peripheral)
        4. Read and decode a single register (provide peripheral + register)

        Bitfield values are extracted and annotated with their descriptions,
        making it easy to understand hardware configuration at a glance.

        Args:
            svd_path: Path to an SVD file to load (optional if already loaded).
            peripheral: Peripheral name to inspect (e.g. "GPIOA", "USART1").
            register: Specific register to read (e.g. "CR1", "SR"). Requires peripheral.
        """
        state = _get_state(ctx)
        require_session(state)

        svd = state.session.svd

        # Load SVD if path provided
        if svd_path:
            path = Path(svd_path).resolve()

            # L3: Validate SVD file
            if not path.exists():
                raise OpenOCDError(f"SVD file not found: {path}")
            if path.suffix.lower() != ".svd":
                raise OpenOCDError(f"Expected .svd file, got '{path.suffix}': {path}")
            file_size = path.stat().st_size
            if file_size > _MAX_SVD_SIZE:
                raise OpenOCDError(
                    f"SVD file is {file_size} bytes, exceeds {_MAX_SVD_SIZE} byte limit"
                )

            await svd.load(path)
            state.svd_path = str(path)

        # List peripherals if none specified
        if peripheral is None:
            peripherals = svd.list_peripherals() if svd.loaded else []
            return SVDListResult(
                peripherals=peripherals,
                svd_loaded=svd.loaded,
            )

        # Read and decode registers for a peripheral
        if register is None:
            await ctx.report_progress(0, 1, f"Reading {peripheral} registers...")
            decoded = await svd.read_peripheral(peripheral)
            registers = {}
            total_regs = len(decoded)
            for i, (reg_name, dr) in enumerate(decoded.items()):
                await ctx.report_progress(i, total_regs, f"Decoding {peripheral}.{reg_name}...")
                registers[reg_name] = SVDRegisterResult(
                    name=dr.register,
                    address=f"0x{dr.address:08x}",
                    raw_value=f"0x{dr.raw_value:08x}",
                    fields=[
                        SVDFieldResult(
                            name=f.name,
                            value=f.value,
                            width=f.width,
                            offset=f.offset,
                            description=f.description,
                        )
                        for f in dr.fields
                    ],
                )
            await ctx.report_progress(total_regs, total_regs, f"Decoded {total_regs} registers")
            return SVDPeripheralResult(peripheral=peripheral, registers=registers)

        # Read a single register
        dr = await svd.read_register(peripheral, register)
        reg_result = SVDRegisterResult(
            name=dr.register,
            address=f"0x{dr.address:08x}",
            raw_value=f"0x{dr.raw_value:08x}",
            fields=[
                SVDFieldResult(
                    name=f.name,
                    value=f.value,
                    width=f.width,
                    offset=f.offset,
                    description=f.description,
                )
                for f in dr.fields
            ],
        )
        return SVDPeripheralResult(
            peripheral=peripheral,
            registers={register: reg_result},
        )
