"""Tests for structured CLI error messages (issue #114)."""

from __future__ import annotations

import pytest

from llmhosts.errors import ERRORS, CLIError, Severity, format_error


class TestErrorCatalog:
    """Verify the error catalog has the required entries."""

    def test_at_least_10_errors(self) -> None:
        assert len(ERRORS) >= 10

    def test_all_errors_have_codes(self) -> None:
        for key, err in ERRORS.items():
            assert err.code, f"Error {key} missing code"
            assert err.code.startswith("E"), f"Error code {err.code} should start with E"

    def test_all_errors_have_required_fields(self) -> None:
        for key, err in ERRORS.items():
            assert err.summary, f"Error {key} missing summary"
            assert err.cause, f"Error {key} missing cause"
            assert err.fix, f"Error {key} missing fix"
            assert isinstance(err.severity, Severity), f"Error {key} has invalid severity"

    def test_error_codes_are_unique(self) -> None:
        codes = [err.code for err in ERRORS.values()]
        assert len(codes) == len(set(codes)), "Duplicate error codes found"

    def test_known_error_keys_exist(self) -> None:
        expected_keys = [
            "PORT_IN_USE",
            "STARTUP_FAILED",
            "INVALID_API_KEY",
            "KEY_VALIDATION_FAILED",
            "CONFIG_PARSE_ERROR",
            "CONFIG_WRITE_ERROR",
            "OLLAMA_UNREACHABLE",
            "NO_BACKENDS",
            "MODEL_NOT_FOUND",
            "DB_MIGRATION_ERROR",
            "TUNNEL_NOT_FOUND",
            "KEY_DECRYPT_ERROR",
            "PERMISSION_DENIED",
        ]
        for key in expected_keys:
            assert key in ERRORS, f"Missing error key: {key}"


class TestSeverity:
    """Verify severity enum values."""

    def test_fatal_is_red(self) -> None:
        assert Severity.FATAL.value == "red"

    def test_warning_is_yellow(self) -> None:
        assert Severity.WARNING.value == "yellow"

    def test_info_is_blue(self) -> None:
        assert Severity.INFO.value == "blue"


class TestFormatError:
    """Verify error message formatting."""

    def test_format_known_error(self) -> None:
        result = format_error("PORT_IN_USE", host="0.0.0.0", port="4000", alt_port="4001")
        assert "E001" in result
        assert "Port already in use" in result
        assert "0.0.0.0:4000" in result
        assert "4001" in result

    def test_format_with_interpolation(self) -> None:
        result = format_error("STARTUP_FAILED", detail="connection refused")
        assert "E002" in result
        assert "connection refused" in result

    def test_format_unknown_error_key(self) -> None:
        result = format_error("NONEXISTENT_ERROR", detail="something broke")
        assert "something broke" in result

    def test_format_missing_kwargs_graceful(self) -> None:
        # Missing template variables should not crash, just leave {var} in output
        result = format_error("PORT_IN_USE")
        assert "E001" in result
        assert "Port already in use" in result

    def test_format_with_debug_traceback(self) -> None:
        try:
            raise ValueError("test error")
        except ValueError as exc:
            result = format_error("STARTUP_FAILED", debug=True, exc=exc, detail="test error")
            assert "Debug traceback" in result
            assert "ValueError" in result

    def test_format_without_debug_no_traceback(self) -> None:
        try:
            raise ValueError("test error")
        except ValueError as exc:
            result = format_error("STARTUP_FAILED", debug=False, exc=exc, detail="test error")
            assert "Debug traceback" not in result

    def test_severity_colors_in_output(self) -> None:
        # Fatal errors use red
        result = format_error("PORT_IN_USE", host="x", port="4000", alt_port="4001")
        assert "[red bold]" in result

        # Warning errors use yellow
        result = format_error("NO_BACKENDS")
        assert "[yellow bold]" in result

    def test_fix_section_present(self) -> None:
        result = format_error("OLLAMA_UNREACHABLE", host="http://localhost:11434")
        assert "Fix:" in result
        assert "inference engine" in result.lower()

    def test_cause_section_present(self) -> None:
        result = format_error("CONFIG_PARSE_ERROR", path="/tmp/config.toml", detail="invalid syntax")
        assert "Cause:" in result
        assert "invalid syntax" in result


class TestCLIError:
    """Verify CLIError dataclass."""

    def test_frozen(self) -> None:
        err = CLIError(code="E999", summary="Test", cause="test", fix="test")
        with pytest.raises(AttributeError):
            err.code = "E000"  # type: ignore[misc]

    def test_default_severity_is_fatal(self) -> None:
        err = CLIError(code="E999", summary="Test", cause="test", fix="test")
        assert err.severity == Severity.FATAL
