"""Tests for llmhosts.discovery.autoconfig — config generation from discovered engines."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest
import toml

from llmhosts.discovery.autoconfig import AutoConfigurator
from llmhosts.discovery.engine import EngineType, InferenceEngine

if TYPE_CHECKING:
    from pathlib import Path


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


def _make_engine(
    engine_type: EngineType = EngineType.OLLAMA,
    host: str = "http://127.0.0.1:11434",
    available: bool = True,
    models: list[str] | None = None,
    throughput_tps: float | None = None,
) -> InferenceEngine:
    return InferenceEngine(
        engine_type=engine_type,
        host=host,
        available=available,
        models=models or [],
        throughput_tps=throughput_tps,
    )


# ---------------------------------------------------------------------------
# generate()
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestGenerate:
    def test_single_ollama_engine(self) -> None:
        engine = _make_engine(models=["llama3:8b", "codellama:7b"])
        config = AutoConfigurator().generate([engine])

        assert "backends" in config
        assert len(config["backends"]) == 1
        be = config["backends"][0]
        assert be["host"] == "http://127.0.0.1:11434"
        assert be["engine_type"] == "ollama"
        assert be["api_compat"] == "ollama"
        assert be["models"] == ["llama3:8b", "codellama:7b"]

    def test_single_vllm_engine(self) -> None:
        engine = _make_engine(
            engine_type=EngineType.VLLM,
            host="http://localhost:8000",
            models=["meta-llama/Llama-3-8B"],
            throughput_tps=85.0,
        )
        config = AutoConfigurator().generate([engine])

        be = config["backends"][0]
        assert be["api_compat"] == "openai"
        assert be["throughput_tps"] == 85.0

    def test_multiple_engines(self) -> None:
        engines = [
            _make_engine(models=["llama3:8b"]),
            _make_engine(
                engine_type=EngineType.VLLM,
                host="http://localhost:8000",
                models=["meta-llama/Llama-3-8B"],
            ),
        ]
        config = AutoConfigurator().generate(engines)
        assert len(config["backends"]) == 2

    def test_unavailable_engine_skipped(self) -> None:
        engine = _make_engine(available=False, models=["llama3:8b"])
        config = AutoConfigurator().generate([engine])
        assert config.get("backends", []) == []

    def test_empty_engine_list_produces_minimal_config(self) -> None:
        config = AutoConfigurator().generate([])
        assert "server" in config
        assert "discovery" in config
        assert config["discovery"]["interval_seconds"] == 300
        assert config.get("backends", []) == []

    def test_no_models_omits_models_key(self) -> None:
        engine = _make_engine(models=[])
        config = AutoConfigurator().generate([engine])
        be = config["backends"][0]
        assert "models" not in be

    def test_no_throughput_omits_key(self) -> None:
        engine = _make_engine(throughput_tps=None)
        config = AutoConfigurator().generate([engine])
        be = config["backends"][0]
        assert "throughput_tps" not in be


# ---------------------------------------------------------------------------
# generate() — merge with existing
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestMergeExisting:
    def test_preserves_existing_server_config(self) -> None:
        existing = {"server": {"port": 9999, "host": "0.0.0.0"}}
        engine = _make_engine(models=["llama3:8b"])
        config = AutoConfigurator().generate([engine], existing_config=existing)

        assert config["server"]["port"] == 9999
        assert config["server"]["host"] == "0.0.0.0"

    def test_does_not_overwrite_existing_backend(self) -> None:
        existing = {"backends": [{"host": "http://127.0.0.1:11434", "api_compat": "custom", "my_flag": True}]}
        engine = _make_engine(models=["llama3:8b"])
        config = AutoConfigurator().generate([engine], existing_config=existing)

        assert len(config["backends"]) == 1
        be = config["backends"][0]
        # Original values untouched
        assert be["api_compat"] == "custom"
        assert be["my_flag"] is True

    def test_adds_new_backend_alongside_existing(self) -> None:
        existing = {"backends": [{"host": "http://127.0.0.1:11434", "api_compat": "ollama"}]}
        new_engine = _make_engine(
            engine_type=EngineType.VLLM,
            host="http://localhost:8000",
            models=["llama3"],
        )
        config = AutoConfigurator().generate([new_engine], existing_config=existing)
        assert len(config["backends"]) == 2

    def test_preserves_existing_discovery_interval(self) -> None:
        existing = {"discovery": {"interval_seconds": 600}}
        config = AutoConfigurator().generate([], existing_config=existing)
        assert config["discovery"]["interval_seconds"] == 600

    def test_host_trailing_slash_normalization(self) -> None:
        """Hosts with and without trailing slashes should match."""
        existing = {"backends": [{"host": "http://127.0.0.1:11434/"}]}
        engine = _make_engine(host="http://127.0.0.1:11434")
        config = AutoConfigurator().generate([engine], existing_config=existing)
        # Should not add a duplicate
        assert len(config["backends"]) == 1

    def test_does_not_mutate_existing_dict(self) -> None:
        existing = {"server": {"port": 4000}}
        import copy

        snapshot = copy.deepcopy(existing)
        AutoConfigurator().generate([_make_engine()], existing_config=existing)
        assert existing == snapshot


# ---------------------------------------------------------------------------
# write_config() / round-trip
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestWriteConfig:
    def test_write_creates_file(self, tmp_path: Path) -> None:
        target = tmp_path / "sub" / "config.toml"
        config = {"server": {"port": 4000}}
        result = AutoConfigurator().write_config(config, target)
        assert result == target
        assert target.is_file()

    def test_round_trip(self, tmp_path: Path) -> None:
        engine = _make_engine(models=["llama3:8b"])
        original = AutoConfigurator().generate([engine])

        target = tmp_path / "config.toml"
        AutoConfigurator().write_config(original, target)

        loaded = toml.load(target)
        assert loaded["backends"][0]["host"] == "http://127.0.0.1:11434"
        assert loaded["backends"][0]["api_compat"] == "ollama"
        assert loaded["backends"][0]["models"] == ["llama3:8b"]
        assert loaded["discovery"]["interval_seconds"] == 300


# ---------------------------------------------------------------------------
# auto_configure() — full pipeline
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestAutoConfigure:
    def test_creates_config_from_scratch(self, tmp_path: Path) -> None:
        target = tmp_path / "config.toml"
        engine = _make_engine(models=["llama3:8b"])

        result = AutoConfigurator().auto_configure([engine], path=target)
        assert result == target

        loaded = toml.load(target)
        assert len(loaded["backends"]) == 1
        assert loaded["backends"][0]["host"] == "http://127.0.0.1:11434"

    def test_merges_with_existing_file(self, tmp_path: Path) -> None:
        target = tmp_path / "config.toml"
        # Write an existing config with a custom server port
        existing = {"server": {"port": 9999}}
        target.write_text(toml.dumps(existing), encoding="utf-8")

        engine = _make_engine(models=["llama3:8b"])
        AutoConfigurator().auto_configure([engine], path=target)

        loaded = toml.load(target)
        assert loaded["server"]["port"] == 9999
        assert len(loaded["backends"]) == 1

    def test_handles_corrupt_existing_file(self, tmp_path: Path) -> None:
        target = tmp_path / "config.toml"
        target.write_text("{{{{invalid toml", encoding="utf-8")

        engine = _make_engine(models=["llama3:8b"])
        result = AutoConfigurator().auto_configure([engine], path=target)
        assert result == target

        loaded = toml.load(target)
        assert len(loaded["backends"]) == 1

    def test_empty_engines_creates_minimal(self, tmp_path: Path) -> None:
        target = tmp_path / "config.toml"
        AutoConfigurator().auto_configure([], path=target)

        loaded = toml.load(target)
        assert "server" in loaded
        assert "discovery" in loaded
        assert loaded.get("backends", []) == []

    def test_default_path_property(self) -> None:
        """DEFAULT_CONFIG_PATH should point to a .toml file."""
        assert AutoConfigurator.DEFAULT_CONFIG_PATH.endswith("config.toml")
