"""Local model scanner and remote engine probe base class.

LLMHosts Core is the primary inference engine.  The scanner now discovers
local GGUF models in ``~/.llmhosts/models/`` and converts them into
:class:`DiscoveredEngine` entries.  The :class:`EngineProbe` ABC is retained
for marketplace remote probing — remote backends still expose HTTP APIs.
"""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, ClassVar

from llmhosts.discovery.models import DiscoveredEngine, DiscoveredModel

if TYPE_CHECKING:
    import httpx

    from llmhosts.remit import RemitWatcher

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Abstract probe interface (kept for marketplace remote probing)
# ---------------------------------------------------------------------------


class EngineProbe(ABC):
    """Base class for engine-specific detection probes."""

    engine_type: str = ""
    default_ports: ClassVar[list[int]] = []
    api_compat: str = "openai"

    @abstractmethod
    async def probe(self, host: str, port: int, client: httpx.AsyncClient) -> DiscoveredEngine | None:
        """Probe a host:port and return a DiscoveredEngine if detected, else None."""

    def _make_engine(
        self,
        host: str,
        port: int,
        *,
        models: list[DiscoveredModel] | None = None,
        confidence: float = 1.0,
        version: str = "",
        features: list[str] | None = None,
    ) -> DiscoveredEngine:
        return DiscoveredEngine(
            engine_type=self.engine_type,
            host=host,
            port=port,
            available=True,
            models=models or [],
            api_compat=self.api_compat,
            confidence=confidence,
            version=version,
            features=features or ["streaming"],
        )


# ---------------------------------------------------------------------------
# UniversalScanner — now scans local models instead of engine ports
# ---------------------------------------------------------------------------


class UniversalScanner:
    """Discovers locally-downloaded models and converts them to DiscoveredEngine entries.

    This replaces the old port-scanning approach.  LLMHosts Core is the sole
    local inference engine; the scanner simply enumerates GGUF files in the
    models directory.
    """

    def __init__(
        self,
        host: str = "127.0.0.1",
        extra_hosts: list[str] | None = None,
        timeout: float = 3.0,
    ) -> None:
        self._host = host
        self._extra_hosts = extra_hosts or []
        self._timeout = timeout

    async def scan(self) -> list[DiscoveredEngine]:
        """Return a single DiscoveredEngine for LLMHosts Core with all local models."""
        from llmhosts.models.download import list_local_models

        local = list_local_models()
        if not local:
            return []

        models = [DiscoveredModel(id=name, engine_type="llmhosts-core") for name in local]
        engine = DiscoveredEngine(
            engine_type="llmhosts-core",
            host=self._host,
            port=0,  # Core engine is in-process, no port
            available=True,
            models=models,
            api_compat="openai",
            confidence=1.0,
            features=["streaming", "tool_calling"],
        )
        return [engine]

    @staticmethod
    def _parse_host_port(url: str) -> tuple[str, int]:
        """Parse 'host:port' or 'http://host:port' into (host, port)."""
        url = url.strip()
        for prefix in ("http://", "https://"):
            if url.startswith(prefix):
                url = url[len(prefix) :]
        url = url.rstrip("/")
        if ":" in url:
            parts = url.rsplit(":", 1)
            try:
                return parts[0], int(parts[1])
            except ValueError:
                pass
        return "", 0


def remit_filter(
    engines: list[DiscoveredEngine],
    remit_watcher: RemitWatcher,
) -> list[DiscoveredEngine]:
    """Filter discovered engines to only those within the current remit."""
    return [
        engine
        for engine in engines
        if remit_watcher.is_backend_allowed(
            engine.base_url,
            engine.gpu_ids[0] if engine.gpu_ids else "",
        )
    ]
