"""Auto-generate optimal config.toml from discovered inference engines.

Non-destructive merge: never overwrites user-set values, only adds missing
entries for newly discovered engines and sets sensible defaults.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any, ClassVar

import toml  # type: ignore[import-untyped]

from llmhosts.config import _config_path
from llmhosts.discovery.engine import EngineType

if TYPE_CHECKING:
    from pathlib import Path

    from llmhosts.discovery.engine import InferenceEngine

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

_API_COMPAT_MAP: dict[str, str] = {
    EngineType.OLLAMA.value: "ollama",
    EngineType.VLLM.value: "openai",
}

_DEFAULT_DISCOVERY_INTERVAL: int = 300  # seconds


# ---------------------------------------------------------------------------
# AutoConfigurator
# ---------------------------------------------------------------------------


class AutoConfigurator:
    """Generate optimal config.toml from discovered engines.

    Non-destructive merge: never overwrites user-set values.
    """

    DEFAULT_CONFIG_PATH: ClassVar[str] = str(_config_path())

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def generate(
        self,
        engines: list[InferenceEngine],
        existing_config: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Generate config dict from discovered engines, merging with existing.

        Parameters
        ----------
        engines:
            Discovered inference engines from :meth:`EngineManager.discover_all`.
        existing_config:
            Current config dict (from TOML). ``None`` means start from scratch.

        Returns
        -------
        dict
            Merged config suitable for writing to TOML.
        """
        config: dict[str, Any] = dict(existing_config) if existing_config else {}

        # Ensure top-level sections exist
        config.setdefault("server", {})
        config.setdefault("discovery", {})

        # Set discovery interval if not already configured
        config["discovery"].setdefault("interval_seconds", _DEFAULT_DISCOVERY_INTERVAL)

        # Build backends list, preserving existing entries
        existing_backends: list[dict[str, Any]] = config.get("backends", [])
        existing_hosts = {self._backend_key(b) for b in existing_backends}

        for engine in engines:
            if not engine.available:
                continue

            key = self._engine_key(engine)
            if key in existing_hosts:
                logger.debug("Backend %s already configured, skipping", key)
                continue

            backend_entry = self._engine_to_backend(engine)
            existing_backends.append(backend_entry)
            logger.info("Added backend: %s (%s)", key, engine.engine_type.value)

        if existing_backends:
            config["backends"] = existing_backends

        return config

    def write_config(self, config: dict[str, Any], path: Path | None = None) -> Path:
        """Write config to TOML file.

        Parameters
        ----------
        config:
            Config dict to serialize.
        path:
            Target file path. Defaults to ``~/.llmhosts/config.toml``.

        Returns
        -------
        Path
            The path that was written to.
        """
        from pathlib import Path as _Path

        target = path or _Path(self.DEFAULT_CONFIG_PATH)
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text(toml.dumps(config), encoding="utf-8")
        logger.info("Wrote config to %s", target)
        return target

    def auto_configure(
        self,
        engines: list[InferenceEngine],
        path: Path | None = None,
    ) -> Path:
        """Full pipeline: read existing config, merge discoveries, write back.

        Parameters
        ----------
        engines:
            Discovered inference engines.
        path:
            Config file path. Defaults to ``~/.llmhosts/config.toml``.

        Returns
        -------
        Path
            The path that was written to.
        """
        from pathlib import Path as _Path

        target = path or _Path(self.DEFAULT_CONFIG_PATH)

        existing: dict[str, Any] | None = None
        if target.is_file():
            try:
                existing = toml.load(target)
            except toml.TomlDecodeError as exc:
                logger.warning("Failed to parse existing config %s: %s", target, exc)

        merged = self.generate(engines, existing)
        return self.write_config(merged, target)

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _engine_key(engine: InferenceEngine) -> str:
        """Unique key for an engine based on host."""
        return engine.host.rstrip("/")

    @staticmethod
    def _backend_key(backend: dict[str, Any]) -> str:
        """Unique key for an existing backend entry based on host."""
        return str(backend.get("host", "")).rstrip("/")

    @staticmethod
    def _engine_to_backend(engine: InferenceEngine) -> dict[str, Any]:
        """Convert an InferenceEngine to a backends table entry."""
        api_compat = _API_COMPAT_MAP.get(engine.engine_type.value, "openai")
        entry: dict[str, Any] = {
            "host": engine.host,
            "engine_type": engine.engine_type.value,
            "api_compat": api_compat,
        }
        if engine.models:
            entry["models"] = engine.models
        if engine.throughput_tps is not None:
            entry["throughput_tps"] = engine.throughput_tps
        return entry
