"""Tests for protocol_version module (issue #15)"""

from __future__ import annotations

import contextlib

import pytest

from sagellm_protocol import (
    BACKWARD_COMPATIBLE_SINCE,
    CURRENT_VERSION,
    FROZEN_INTERFACES,
    PROTOCOL_VERSION,
    ProtocolVersion,
    get_interface_since_version,
    is_interface_frozen,
    negotiate_version,
)


class TestProtocolVersionConstants:
    def test_protocol_version_is_string(self) -> None:
        assert isinstance(PROTOCOL_VERSION, str)
        assert "." in PROTOCOL_VERSION

    def test_current_version_is_protocol_version_instance(self) -> None:
        assert isinstance(CURRENT_VERSION, ProtocolVersion)

    def test_current_version_matches_string(self) -> None:
        assert str(CURRENT_VERSION) == PROTOCOL_VERSION

    def test_frozen_interfaces_not_empty(self) -> None:
        assert len(FROZEN_INTERFACES) > 0

    def test_backward_compatible_since_not_empty(self) -> None:
        assert len(BACKWARD_COMPATIBLE_SINCE) > 0


class TestProtocolVersionParse:
    def test_parse_valid_version(self) -> None:
        v = ProtocolVersion.parse("0.1")
        assert v.major == 0
        assert v.minor == 1

    def test_parse_large_version(self) -> None:
        v = ProtocolVersion.parse("2.10")
        assert v.major == 2
        assert v.minor == 10

    def test_parse_invalid_format_raises(self) -> None:
        with pytest.raises(ValueError):
            ProtocolVersion.parse("0")

    def test_parse_three_parts_raises(self) -> None:
        with pytest.raises(ValueError):
            ProtocolVersion.parse("0.1.2")

    def test_parse_non_integer_raises(self) -> None:
        with pytest.raises(ValueError):
            ProtocolVersion.parse("a.b")

    def test_str_representation(self) -> None:
        v = ProtocolVersion(major=0, minor=1)
        assert str(v) == "0.1"


class TestProtocolVersionCompatibility:
    def test_same_version_compatible(self) -> None:
        v = ProtocolVersion(0, 1)
        assert v.is_compatible_with(ProtocolVersion(0, 1)) is True

    def test_higher_minor_compatible(self) -> None:
        v = ProtocolVersion(0, 2)
        assert v.is_compatible_with(ProtocolVersion(0, 1)) is True

    def test_lower_minor_not_compatible(self) -> None:
        v = ProtocolVersion(0, 1)
        assert v.is_compatible_with(ProtocolVersion(0, 2)) is False

    def test_different_major_not_compatible(self) -> None:
        v = ProtocolVersion(1, 0)
        assert v.is_compatible_with(ProtocolVersion(0, 1)) is False

    def test_ordering(self) -> None:
        assert ProtocolVersion(0, 1) < ProtocolVersion(0, 2)
        assert ProtocolVersion(0, 2) > ProtocolVersion(0, 1)
        assert ProtocolVersion(1, 0) > ProtocolVersion(0, 9)


class TestNegotiateVersion:
    def test_same_version_negotiates(self) -> None:
        result = negotiate_version(PROTOCOL_VERSION)
        assert result == CURRENT_VERSION

    def test_lower_minor_negotiates(self) -> None:
        # Server is 0.1; client asks for 0.0 → server is compatible (0.1 >= 0.0)
        # Only works if CURRENT_VERSION.minor >= 0
        if CURRENT_VERSION.minor >= 0:
            with contextlib.suppress(ValueError):
                negotiate_version(f"{CURRENT_VERSION.major}.0")

    def test_higher_required_version_raises(self) -> None:
        # Client requires a version higher than server supports
        future_version = f"{CURRENT_VERSION.major}.{CURRENT_VERSION.minor + 100}"
        with pytest.raises(ValueError, match="mismatch"):
            negotiate_version(future_version)

    def test_different_major_raises(self) -> None:
        with pytest.raises(ValueError):
            negotiate_version(f"{CURRENT_VERSION.major + 1}.0")

    def test_invalid_version_string_raises(self) -> None:
        with pytest.raises(ValueError):
            negotiate_version("not-a-version")


class TestFrozenInterfaces:
    def test_known_interfaces_are_frozen(self) -> None:
        frozen = [
            "Request",
            "Response",
            "Metrics",
            "StreamEvent",
            "ErrorCode",
            "Timestamps",
        ]
        for iface in frozen:
            assert is_interface_frozen(iface) is True, f"{iface} should be frozen"

    def test_unknown_interface_not_frozen(self) -> None:
        assert is_interface_frozen("NonExistentInterface") is False

    def test_get_interface_since_version_known(self) -> None:
        since = get_interface_since_version("Request")
        assert since is not None
        ProtocolVersion.parse(since)  # must be a valid version string

    def test_get_interface_since_version_unknown(self) -> None:
        assert get_interface_since_version("UnknownInterface") is None

    def test_extension_interfaces_tracked(self) -> None:
        """New interfaces from issue #16-#21 should be tracked."""
        new_interfaces = [
            "CommGroupInfo",
            "CollectiveRequest",
            "CompressionConfig",
            "KVHookPayload",
            "KVPrefetchParams",
        ]
        for iface in new_interfaces:
            since = get_interface_since_version(iface)
            assert since is not None, f"{iface} should have a 'since' version"
