import glob

import serial.tools.list_ports

from vna_system.core.logging.logger import get_component_logger
from vna_system.core.config import VNA_PID, VNA_VID

logger = get_component_logger(__file__)


class VNAPortLocator:
    """
    Robust VNA serial port locator with in-memory cache.

    Strategy
    --------
    1) Prefer the cached port if it exists *and* matches expected VID/PID.
    2) Scan all ports and pick an exact VID/PID match.
    3) As a last resort, pick the first /dev/ttyACM* (unverified).
    """

    __slots__ = ("_cached_port")

    def __init__(self) -> None:
        self._cached_port: str | None = None
        logger.debug("VNAPortLocator initialized")


    # ---------------------------------------------------------------------
    # Helpers
    # ---------------------------------------------------------------------
    def _enumerate_ports(self) -> list:
        """Return a list of pyserial ListPortInfo entries."""
        try:
            ports = list(serial.tools.list_ports.comports())
            logger.debug("Serial ports enumerated", count=len(ports))
            return ports
        except Exception as exc:
            logger.warning("Failed to enumerate serial ports", error=repr(exc))
            return []

    def _get_port_info(self, device: str):
        """Return pyserial ListPortInfo for a device path, or None if absent."""
        for p in self._enumerate_ports():
            if p.device == device:
                return p
        return None

    @staticmethod
    def _is_exact_vna_port(p) -> bool:
        """True if VID/PID match exactly the expected VNA device."""
        return getattr(p, "vid", None) == VNA_VID and getattr(p, "pid", None) == VNA_PID

    def verify_port_identity(self, port: str) -> bool:
        """
        Verify that a device path belongs to *our* VNA by VID/PID.

        Returns True only if the device exists and matches VID/PID exactly.
        """
        info = self._get_port_info(port)
        if not info:
            logger.debug("Port not present", device=port)
            return False
        if self._is_exact_vna_port(info):
            logger.debug("Port verified by VID/PID", device=port, vid=info.vid, pid=info.pid)
            return True

        logger.warning(
            "Port belongs to a different device",
            device=port,
            vid=getattr(info, "vid", None),
            pid=getattr(info, "pid", None),
            expected_vid=VNA_VID,
            expected_pid=VNA_PID,
        )
        return False

    # ---------------------------------------------------------------------
    # Discovery
    # ---------------------------------------------------------------------
    def find_vna_port(self) -> str | None:
        """
        Locate the VNA serial port following the strategy described in the class docstring.

        Returns
        -------
        str | None
            Device path if found; otherwise None.
        """
        cached = self._cached_port

        # 1) Try the cached port (must be present and VID/PID-correct)
        if cached and self.verify_port_identity(cached):
            logger.info("Using cached VNA port", device=cached)
            return cached
        elif cached:
            logger.debug("Ignoring cached port due to VID/PID mismatch", device=cached)

        # 2) Enumerate ports and pick exact VID/PID match (prefer stable identity)
        exact_candidates: list[str] = []
        for p in self._enumerate_ports():
            logger.debug(
                "Inspecting port",
                device=p.device,
                vid=getattr(p, "vid", None),
                pid=getattr(p, "pid", None),
                manufacturer=getattr(p, "manufacturer", None),
                description=getattr(p, "description", None),
            )
            if self._is_exact_vna_port(p):
                exact_candidates.append(p.device)

        logger.debug("Exact candidates collected", count=len(exact_candidates), candidates=exact_candidates)

        if exact_candidates:
            # If the cached path is among exact matches, keep its priority
            selected = cached if cached in exact_candidates else exact_candidates[0]
            logger.info("VNA device found by VID/PID", device=selected)

            self._cached_port = selected
            logger.debug("Cached port updated", device=selected)
            return selected

        # 3) Last resort: first ACM device (best-effort on Linux; not cached)
        try:
            acm_ports = sorted(glob.glob("/dev/ttyACM*"))
            logger.debug("ACM ports scanned", ports=acm_ports)
            if acm_ports:
                selected = acm_ports[0]
                logger.info("Using first available ACM port (unverified)", device=selected)
                return selected
        except Exception as exc:
            logger.warning("Error during ACM port detection", error=repr(exc))

        logger.warning("VNA device not found by auto-detection")
        return None
