"""Probe and target diagnostics — run before other tools to check capabilities."""

import logging

from fastmcp import Context
from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool

from mcjtag.models import DiagnosticCheck, DiagnosticsResult
from mcjtag.state import SessionState

log = logging.getLogger(__name__)


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


def _check(name: str, ok: bool, detail: str) -> DiagnosticCheck:
    return DiagnosticCheck(name=name, ok=ok, detail=detail)


class DiagnosticsMixin(MCPMixin):
    """Probe and target self-test — call this first to see what's available."""

    @mcp_tool()
    async def probe_diagnostics(self, ctx: Context) -> DiagnosticsResult:
        """Run diagnostics on the probe and target to check what's working.

        Call this first after connecting to understand what capabilities are
        available. Returns a list of checks with pass/fail status covering:

        - OpenOCD connection
        - Adapter/probe identification
        - Transport (SWD/JTAG)
        - Target presence and state
        - Memory access
        - Flash banks
        - SVD file

        Each failed check includes guidance on what to try.
        """
        state = _get_state(ctx)
        checks: list[DiagnosticCheck] = []

        # 1. Connection
        if not state.session:
            checks.append(
                _check(
                    "connection",
                    False,
                    "Not connected. Call connect() or start_openocd() first.",
                )
            )
            return DiagnosticsResult(
                connected=False,
                checks=checks,
                summary="Not connected to OpenOCD.",
            )

        checks.append(_check("connection", True, "Connected to OpenOCD"))
        session = state.session

        # 2. OpenOCD version
        try:
            version = await session.command("version")
            ver_line = version.strip().split("\n")[0][:80]
            checks.append(_check("openocd_version", True, ver_line))
        except Exception as exc:
            checks.append(_check("openocd_version", False, str(exc)[:80]))

        # 3. Adapter info
        try:
            speed = await session.command("adapter speed")
            speed_str = speed.strip()
            checks.append(_check("adapter_speed", True, speed_str))
        except Exception as exc:
            checks.append(_check("adapter_speed", False, str(exc)[:80]))

        # 4. Transport
        try:
            transport = await session.command("transport select")
            checks.append(_check("transport", True, transport.strip()))
        except Exception as exc:
            checks.append(_check("transport", False, str(exc)[:80]))

        # 5. Target presence
        target_ok = False
        try:
            targets_raw = await session.command("targets")
            lines = targets_raw.strip().split("\n")
            # OpenOCD 'targets' output has a header + one line per target
            target_lines = [
                ln
                for ln in lines
                if ln.strip() and not ln.strip().startswith("TargetName") and "---" not in ln
            ]
            if target_lines:
                target_ok = True
                checks.append(
                    _check(
                        "target",
                        True,
                        f"{len(target_lines)} target(s): {target_lines[0].strip()[:60]}",
                    )
                )
            else:
                checks.append(
                    _check(
                        "target",
                        False,
                        "No targets defined. OpenOCD started without a target config.",
                    )
                )
        except Exception as exc:
            checks.append(_check("target", False, str(exc)[:80]))

        # 6. Target state (only if targets exist)
        if target_ok:
            try:
                target_state = await session.target.state()
                state_str = getattr(target_state, "state", "unknown")
                halted = state_str == "halted"
                checks.append(
                    _check(
                        "target_state",
                        True,
                        f"State: {state_str}, halted: {halted}",
                    )
                )
            except Exception as exc:
                checks.append(
                    _check(
                        "target_state",
                        False,
                        f"Cannot read target state: {str(exc)[:60]}",
                    )
                )

        # 7. Memory access (only if target exists)
        if target_ok:
            try:
                vals = await session.memory.read_u32(0xE000ED00, 1)
                cpuid = vals[0]
                checks.append(
                    _check(
                        "memory_access",
                        True,
                        f"CPUID register (SCB): 0x{cpuid:08x}",
                    )
                )
            except Exception as exc:
                checks.append(
                    _check(
                        "memory_access",
                        False,
                        f"Cannot read memory (target may need halting): {str(exc)[:50]}",
                    )
                )

        # 8. Flash banks
        if target_ok:
            try:
                banks = await session.flash.banks()
                if banks:
                    bank = banks[0]
                    checks.append(
                        _check(
                            "flash",
                            True,
                            f"{len(banks)} bank(s), first: {bank.name} "
                            f"@ 0x{bank.base:08x} ({bank.size // 1024} KB)",
                        )
                    )
                else:
                    checks.append(_check("flash", False, "No flash banks found"))
            except Exception as exc:
                checks.append(_check("flash", False, str(exc)[:80]))

        # 9. SVD file
        if state.svd_path:
            checks.append(_check("svd", True, f"Loaded: {state.svd_path}"))
        else:
            checks.append(
                _check(
                    "svd",
                    False,
                    "No SVD loaded. Use svd_inspect(svd_path=...) to load one.",
                )
            )

        # Build summary
        passed = sum(1 for c in checks if c.ok)
        total = len(checks)
        parts = []
        if any(c.name == "connection" and c.ok for c in checks):
            parts.append("connected")
        if target_ok:
            parts.append("target found")
        else:
            parts.append("no target")
        parts.append(f"{passed}/{total} checks passed")

        return DiagnosticsResult(
            connected=True,
            checks=checks,
            summary=", ".join(parts),
        )
