"""Tests for GPU remit system -- controlling which backends LLMHosts can use."""

from __future__ import annotations

from datetime import datetime
from unittest.mock import patch

import pytest

from llmhosts.config import LLMHostsConfig, RemitConfig, RemitScheduleEntry
from llmhosts.discovery.models import DiscoveredEngine
from llmhosts.remit import RemitWatcher, filter_backends_by_remit

# Pydantic v2 + from __future__ import annotations requires model_rebuild()
DiscoveredEngine.model_rebuild()


@pytest.fixture
def default_config() -> LLMHostsConfig:
    return LLMHostsConfig()


@pytest.fixture
def allowlist_config() -> LLMHostsConfig:
    cfg = LLMHostsConfig()
    cfg.remit = RemitConfig(mode="allowlist", allowlist=["ollama-local", "rtx-4090"])
    return cfg


@pytest.fixture
def denylist_config() -> LLMHostsConfig:
    cfg = LLMHostsConfig()
    cfg.remit = RemitConfig(mode="denylist", denylist=["cloud-openai", "work-gpu"])
    return cfg


class TestRemitWatcher:
    """Tests for RemitWatcher backend filtering."""

    def test_default_allows_all(self, default_config: LLMHostsConfig) -> None:
        watcher = RemitWatcher(default_config)
        assert watcher.is_backend_allowed("any-backend")
        assert watcher.is_backend_allowed("ollama-local")
        assert watcher.is_backend_allowed("cloud-openai")

    def test_allowlist_permits_listed(self, allowlist_config: LLMHostsConfig) -> None:
        watcher = RemitWatcher(allowlist_config)
        assert watcher.is_backend_allowed("ollama-local")
        assert watcher.is_backend_allowed("rtx-4090")

    def test_allowlist_blocks_unlisted(self, allowlist_config: LLMHostsConfig) -> None:
        watcher = RemitWatcher(allowlist_config)
        assert not watcher.is_backend_allowed("cloud-openai")
        assert not watcher.is_backend_allowed("some-other-gpu")

    def test_denylist_blocks_listed(self, denylist_config: LLMHostsConfig) -> None:
        watcher = RemitWatcher(denylist_config)
        assert not watcher.is_backend_allowed("cloud-openai")
        assert not watcher.is_backend_allowed("work-gpu")

    def test_denylist_permits_unlisted(self, denylist_config: LLMHostsConfig) -> None:
        watcher = RemitWatcher(denylist_config)
        assert watcher.is_backend_allowed("ollama-local")
        assert watcher.is_backend_allowed("personal-gpu")

    def test_gpu_name_matching(self) -> None:
        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="denylist", denylist=["RTX 3060"])
        watcher = RemitWatcher(cfg)
        assert not watcher.is_backend_allowed("backend-1", gpu_name="NVIDIA RTX 3060 12GB")
        assert watcher.is_backend_allowed("backend-2", gpu_name="NVIDIA RTX 4090")

    def test_case_insensitive_matching(self) -> None:
        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="allowlist", allowlist=["Ollama-Local"])
        watcher = RemitWatcher(cfg)
        assert watcher.is_backend_allowed("ollama-local")
        assert watcher.is_backend_allowed("OLLAMA-LOCAL")

    def test_unknown_mode_defaults_to_allow(self) -> None:
        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="unknown")
        watcher = RemitWatcher(cfg)
        assert watcher.is_backend_allowed("any-backend")

    def test_active_remit_property(self, default_config: LLMHostsConfig) -> None:
        watcher = RemitWatcher(default_config)
        assert watcher.active_remit.mode == "all"

    def test_allowlist_empty_list_blocks_all(self) -> None:
        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="allowlist", allowlist=[])
        watcher = RemitWatcher(cfg)
        assert not watcher.is_backend_allowed("any-backend")


class TestRemitSchedule:
    """Tests for time-based remit scheduling."""

    def test_no_schedule_always_allowed(self) -> None:
        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="all", schedule=[])
        watcher = RemitWatcher(cfg)
        assert watcher.is_backend_allowed("any-backend")

    def test_within_schedule_allowed(self) -> None:
        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(
            mode="all",
            schedule=[
                RemitScheduleEntry(start_hour=0, end_hour=24, days=["mon", "tue", "wed", "thu", "fri", "sat", "sun"])
            ],
        )
        watcher = RemitWatcher(cfg)
        assert watcher.is_backend_allowed("any-backend")

    def test_outside_schedule_blocked(self) -> None:
        cfg = LLMHostsConfig()
        # Schedule only allows 9-17 on weekdays
        cfg.remit = RemitConfig(
            mode="all",
            schedule=[RemitScheduleEntry(start_hour=9, end_hour=17, days=["mon", "tue", "wed", "thu", "fri"])],
        )
        watcher = RemitWatcher(cfg)
        # Mock to a Saturday at 10:00
        mock_now = datetime(2026, 2, 28, 10, 0)  # Saturday
        with patch("llmhosts.remit.datetime") as mock_dt:
            mock_dt.now.return_value = mock_now
            assert not watcher._is_within_schedule(cfg.remit)

    def test_schedule_hour_boundary(self) -> None:
        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(
            mode="all",
            schedule=[
                RemitScheduleEntry(start_hour=9, end_hour=17, days=["mon", "tue", "wed", "thu", "fri", "sat", "sun"])
            ],
        )
        # Test that end_hour is exclusive (17:00 is NOT within 9-17)
        mock_now = datetime(2026, 3, 2, 17, 0)  # Monday 17:00
        with patch("llmhosts.remit.datetime") as mock_dt:
            mock_dt.now.return_value = mock_now
            result = RemitWatcher._is_within_schedule(cfg.remit)
            assert not result  # 17 is NOT < 17

    def test_schedule_start_boundary_included(self) -> None:
        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(
            mode="all",
            schedule=[
                RemitScheduleEntry(start_hour=9, end_hour=17, days=["mon", "tue", "wed", "thu", "fri", "sat", "sun"])
            ],
        )
        # 9:00 on Monday should be within schedule
        mock_now = datetime(2026, 3, 2, 9, 0)  # Monday 09:00
        with patch("llmhosts.remit.datetime") as mock_dt:
            mock_dt.now.return_value = mock_now
            result = RemitWatcher._is_within_schedule(cfg.remit)
            assert result


class TestFilterBackends:
    """Tests for the filter_backends_by_remit helper."""

    def test_filter_with_allowlist(self) -> None:
        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="allowlist", allowlist=["backend-a", "backend-c"])
        watcher = RemitWatcher(cfg)

        backends = ["backend-a", "backend-b", "backend-c", "backend-d"]
        filtered = filter_backends_by_remit(backends, watcher)
        assert filtered == ["backend-a", "backend-c"]

    def test_filter_with_denylist(self) -> None:
        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="denylist", denylist=["backend-b"])
        watcher = RemitWatcher(cfg)

        backends = ["backend-a", "backend-b", "backend-c"]
        filtered = filter_backends_by_remit(backends, watcher)
        assert filtered == ["backend-a", "backend-c"]

    def test_filter_with_gpu_names(self) -> None:
        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="denylist", denylist=["RTX 3060"])
        watcher = RemitWatcher(cfg)

        backends = ["b1", "b2"]
        gpu_names = {"b1": "NVIDIA RTX 4090", "b2": "NVIDIA RTX 3060"}
        filtered = filter_backends_by_remit(backends, watcher, gpu_names)
        assert filtered == ["b1"]

    def test_filter_all_mode_returns_all(self) -> None:
        cfg = LLMHostsConfig()
        watcher = RemitWatcher(cfg)

        backends = ["a", "b", "c"]
        filtered = filter_backends_by_remit(backends, watcher)
        assert filtered == ["a", "b", "c"]

    def test_filter_empty_list(self) -> None:
        cfg = LLMHostsConfig()
        watcher = RemitWatcher(cfg)

        filtered = filter_backends_by_remit([], watcher)
        assert filtered == []


class TestRemitCLI:
    """Tests for remit CLI commands."""

    def test_remit_show_runs(self) -> None:
        from click.testing import CliRunner

        from llmhosts.cli import main

        runner = CliRunner()
        result = runner.invoke(main, ["remit", "show"])
        assert result.exit_code == 0
        assert "Remit Mode" in result.output

    def test_remit_help(self) -> None:
        from click.testing import CliRunner

        from llmhosts.cli import main

        runner = CliRunner()
        result = runner.invoke(main, ["remit", "--help"])
        assert result.exit_code == 0
        assert "remit" in result.output.lower()

    def test_remit_allow_help(self) -> None:
        from click.testing import CliRunner

        from llmhosts.cli import main

        runner = CliRunner()
        result = runner.invoke(main, ["remit", "allow", "--help"])
        assert result.exit_code == 0

    def test_remit_deny_help(self) -> None:
        from click.testing import CliRunner

        from llmhosts.cli import main

        runner = CliRunner()
        result = runner.invoke(main, ["remit", "deny", "--help"])
        assert result.exit_code == 0

    def test_remit_schedule_help(self) -> None:
        from click.testing import CliRunner

        from llmhosts.cli import main

        runner = CliRunner()
        result = runner.invoke(main, ["remit", "schedule", "--help"])
        assert result.exit_code == 0
        assert "--start" in result.output
        assert "--end" in result.output


class TestRemitConfig:
    """Tests for RemitConfig model validation."""

    def test_default_config_has_remit(self) -> None:
        cfg = LLMHostsConfig()
        assert cfg.remit.mode == "all"
        assert cfg.remit.allowlist == []
        assert cfg.remit.denylist == []
        assert cfg.remit.schedule == []
        assert cfg.remit.hot_reload is True

    def test_schedule_entry_validation(self) -> None:
        entry = RemitScheduleEntry(start_hour=9, end_hour=17, days=["mon", "fri"])
        assert entry.start_hour == 9
        assert entry.end_hour == 17
        assert entry.days == ["mon", "fri"]

    def test_schedule_entry_defaults(self) -> None:
        entry = RemitScheduleEntry()
        assert entry.start_hour == 0
        assert entry.end_hour == 24
        assert len(entry.days) == 7


class TestDispatcherRemit:
    """Tests for RemitWatcher integration with BackendDispatcher."""

    def test_dispatcher_in_remit_without_watcher(self) -> None:
        from llmhosts.proxy.dispatcher import BackendDispatcher, BackendEntry

        dispatcher = BackendDispatcher()
        entry = BackendEntry(name="test", base_url="http://localhost:11434", backend_type="ollama")
        assert dispatcher._in_remit(entry)

    def test_dispatcher_in_remit_with_watcher_allows(self) -> None:
        from llmhosts.proxy.dispatcher import BackendDispatcher, BackendEntry

        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="allowlist", allowlist=["test-backend"])
        watcher = RemitWatcher(cfg)

        dispatcher = BackendDispatcher()
        dispatcher.attach_remit_watcher(watcher)

        entry = BackendEntry(name="test-backend", base_url="http://localhost:11434", backend_type="ollama")
        assert dispatcher._in_remit(entry)

    def test_dispatcher_in_remit_with_watcher_blocks(self) -> None:
        from llmhosts.proxy.dispatcher import BackendDispatcher, BackendEntry

        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="allowlist", allowlist=["other-backend"])
        watcher = RemitWatcher(cfg)

        dispatcher = BackendDispatcher()
        dispatcher.attach_remit_watcher(watcher)

        entry = BackendEntry(name="test-backend", base_url="http://localhost:11434", backend_type="ollama")
        assert not dispatcher._in_remit(entry)


class TestScannerRemitFilter:
    """Tests for remit_filter in scanner module."""

    def test_remit_filter_allows_all(self) -> None:
        from llmhosts.discovery.scanner import remit_filter

        cfg = LLMHostsConfig()
        watcher = RemitWatcher(cfg)

        engines = [
            DiscoveredEngine(engine_type="ollama", host="127.0.0.1", port=11434),
            DiscoveredEngine(engine_type="vllm", host="127.0.0.1", port=8000),
        ]
        result = remit_filter(engines, watcher)
        assert len(result) == 2

    def test_remit_filter_denylist(self) -> None:
        from llmhosts.discovery.scanner import remit_filter

        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="denylist", denylist=["127.0.0.1:8000"])
        watcher = RemitWatcher(cfg)

        engines = [
            DiscoveredEngine(engine_type="ollama", host="127.0.0.1", port=11434),
            DiscoveredEngine(engine_type="vllm", host="127.0.0.1", port=8000),
        ]
        result = remit_filter(engines, watcher)
        assert len(result) == 1
        assert result[0].engine_type == "ollama"

    def test_remit_filter_with_gpu_ids(self) -> None:
        from llmhosts.discovery.scanner import remit_filter

        cfg = LLMHostsConfig()
        cfg.remit = RemitConfig(mode="denylist", denylist=["RTX 3060"])
        watcher = RemitWatcher(cfg)

        engines = [
            DiscoveredEngine(engine_type="ollama", host="127.0.0.1", port=11434, gpu_ids=["NVIDIA RTX 4090"]),
            DiscoveredEngine(engine_type="vllm", host="127.0.0.1", port=8000, gpu_ids=["NVIDIA RTX 3060"]),
        ]
        result = remit_filter(engines, watcher)
        assert len(result) == 1
        assert result[0].gpu_ids == ["NVIDIA RTX 4090"]
