"""Tests for version compatibility module."""

import warnings

import pytest

from dotpromptz.typing import ParsedPrompt
from dotpromptz.version import (
    CURRENT_MAJOR,
    PromptVersionError,
    _ADAPTERS,
    adapt_prompt,
    register_adapter,
)


def _make_prompt(*, version: int | None = None) -> ParsedPrompt:
    """Create a minimal ParsedPrompt with the given version."""
    return ParsedPrompt(
        version=version,
        ext={},
        config=None,
        metadata={},
        tool_defs=None,
        template='Hello {{name}}',
    )


class TestCurrentMajor:
    """Sanity checks for the CURRENT_MAJOR constant."""

    def test_is_positive_int(self):
        assert isinstance(CURRENT_MAJOR, int)
        assert CURRENT_MAJOR >= 1


class TestAdaptPromptExactMatch:
    """Tests for prompts matching the current engine version."""

    def test_explicit_current_version_passes(self):
        prompt = _make_prompt(version=CURRENT_MAJOR)
        result = adapt_prompt(prompt)
        assert result is prompt  # no copy, same object

    def test_missing_version_defaults_to_current(self):
        prompt = _make_prompt(version=None)
        result = adapt_prompt(prompt)
        assert result is prompt


class TestAdaptPromptForwardIncompatible:
    """Tests for prompts newer than the engine."""

    def test_version_greater_than_current_raises(self):
        prompt = _make_prompt(version=CURRENT_MAJOR + 1)
        with pytest.raises(PromptVersionError) as exc_info:
            adapt_prompt(prompt)
        assert exc_info.value.prompt_version == CURRENT_MAJOR + 1
        assert exc_info.value.engine_version == CURRENT_MAJOR

    def test_far_future_version_raises(self):
        prompt = _make_prompt(version=CURRENT_MAJOR + 99)
        with pytest.raises(PromptVersionError):
            adapt_prompt(prompt)

    def test_error_message_contains_versions(self):
        prompt = _make_prompt(version=CURRENT_MAJOR + 1)
        with pytest.raises(PromptVersionError, match=rf'v{CURRENT_MAJOR + 1}\.x'):
            adapt_prompt(prompt)


class TestAdaptPromptBackwardCompatible:
    """Tests for prompts older than the engine (migration path)."""

    def test_no_adapter_registered_raises(self):
        """When no migration adapter exists, should raise."""
        prompt = _make_prompt(version=0)
        with pytest.raises(PromptVersionError):
            adapt_prompt(prompt)


class TestRegisterAdapter:
    """Tests for the adapter registration decorator."""

    def test_register_and_invoke(self):
        """Register a mock adapter and verify it gets called during migration."""
        # Temporarily bump CURRENT_MAJOR to simulate a v2 engine
        import dotpromptz.version as mod

        original_major = mod.CURRENT_MAJOR
        original_adapters = dict(_ADAPTERS)

        try:
            mod.CURRENT_MAJOR = 2

            @register_adapter(1)
            def _adapt_v1_to_v2(prompt: ParsedPrompt) -> ParsedPrompt:
                return prompt.model_copy(update={'version': 2})

            prompt = _make_prompt(version=1)
            with warnings.catch_warnings(record=True) as w:
                warnings.simplefilter('always')
                result = adapt_prompt(prompt)

            assert result.version == 2
            # Should have emitted a DeprecationWarning
            assert any(issubclass(warning.category, DeprecationWarning) for warning in w)
        finally:
            mod.CURRENT_MAJOR = original_major
            _ADAPTERS.clear()
            _ADAPTERS.update(original_adapters)

    def test_chain_migration(self):
        """Register v1→v2 and v2→v3 adapters and verify chain migration."""
        import dotpromptz.version as mod

        original_major = mod.CURRENT_MAJOR
        original_adapters = dict(_ADAPTERS)

        try:
            mod.CURRENT_MAJOR = 3

            @register_adapter(1)
            def _adapt_v1_to_v2(prompt: ParsedPrompt) -> ParsedPrompt:
                return prompt.model_copy(update={'version': 2})

            @register_adapter(2)
            def _adapt_v2_to_v3(prompt: ParsedPrompt) -> ParsedPrompt:
                return prompt.model_copy(update={'version': 3})

            prompt = _make_prompt(version=1)
            with warnings.catch_warnings(record=True) as w:
                warnings.simplefilter('always')
                result = adapt_prompt(prompt)

            assert result.version == 3
            # Should have emitted 2 deprecation warnings (v1→v2 and v2→v3)
            dep_warnings = [x for x in w if issubclass(x.category, DeprecationWarning)]
            assert len(dep_warnings) == 2
        finally:
            mod.CURRENT_MAJOR = original_major
            _ADAPTERS.clear()
            _ADAPTERS.update(original_adapters)


class TestVersionInParsedPrompt:
    """Tests that the version field is properly parsed from frontmatter."""

    def test_version_field_parsed(self):
        from dotpromptz.parse import parse_document

        source = '---\nversion: 1\n---\nHello'
        result = parse_document(source)
        assert result.version == 1

    def test_no_version_field_is_none(self):
        from dotpromptz.parse import parse_document

        source = '---\nconfig:\n  model: gpt-4o\n---\nHello'
        result = parse_document(source)
        assert result.version is None

    def test_version_not_in_ext(self):
        """version should be a reserved keyword, not ending up in ext."""
        from dotpromptz.parse import parse_document

        source = '---\nversion: 1\n---\nHello'
        result = parse_document(source)
        assert 'version' not in (result.ext or {})
