# tests/db/test_QdrantSchema.py
#  Copyright © 2025 Dr.-Ing. Paul Wilhelm <paul@wilhelm.dev>
#  This file is part of Archive Agent. See LICENSE for details.

import pytest
from pydantic import ValidationError

from archive_agent.db.QdrantSchema import QdrantPayload, parse_payload


class TestQdrantPayload:
    """Test suite for QdrantPayload Pydantic model."""

    def test_valid_payload_all_fields(self):
        """Test creating a valid payload with all fields."""
        payload = QdrantPayload(
            file_path="/home/user/test.txt",
            file_mtime=1640995200.0,
            chunk_index=5,
            chunks_total=10,
            chunk_text="This is test chunk content.",
            version="v1.0.0",
            page_range=[1, 3],
            line_range=None
        )

        assert payload.file_path == "/home/user/test.txt"
        assert payload.file_mtime == 1640995200.0
        assert payload.chunk_index == 5
        assert payload.chunks_total == 10
        assert payload.chunk_text == "This is test chunk content."
        assert payload.version == "v1.0.0"
        assert payload.page_range == [1, 3]
        assert payload.line_range is None

    def test_valid_payload_mandatory_only(self):
        """Test creating a valid payload with only mandatory fields."""
        payload = QdrantPayload(
            file_path="/home/user/test.txt",
            file_mtime=1640995200.0,
            chunk_index=5,
            chunks_total=10,
            chunk_text="This is test chunk content.",
            version=None,
            page_range=None,
            line_range=None
        )

        assert payload.file_path == "/home/user/test.txt"
        assert payload.file_mtime == 1640995200.0
        assert payload.chunk_index == 5
        assert payload.chunks_total == 10
        assert payload.chunk_text == "This is test chunk content."
        assert payload.version is None
        assert payload.page_range is None
        assert payload.line_range is None

    def test_valid_payload_with_line_range(self):
        """Test creating a valid payload with line_range but no page_range."""
        payload = QdrantPayload(
            file_path="/home/user/test.txt",
            file_mtime=1640995200.0,
            chunk_index=5,
            chunks_total=10,
            chunk_text="This is test chunk content.",
            version="v1.0.0",
            page_range=None,
            line_range=[10, 15]
        )

        assert payload.line_range == [10, 15]
        assert payload.page_range is None

    def test_wrong_type_chunk_index_via_parse_payload(self):
        """Test that wrong type for chunk_index raises ValidationError when parsing."""
        invalid_payload_dict = {
            "file_path": "/home/user/test.txt",
            "file_mtime": 1640995200.0,
            "chunk_index": "not_an_int",  # Wrong type
            "chunks_total": 10,
            "chunk_text": "This is test chunk content."
        }

        with pytest.raises(ValidationError) as exc_info:
            parse_payload(invalid_payload_dict)

        assert "chunk_index" in str(exc_info.value)

    def test_wrong_type_file_mtime_via_parse_payload(self):
        """Test that wrong type for file_mtime raises ValidationError when parsing."""
        invalid_payload_dict = {
            "file_path": "/home/user/test.txt",
            "file_mtime": "not_a_float",  # Wrong type
            "chunk_index": 5,
            "chunks_total": 10,
            "chunk_text": "This is test chunk content."
        }

        with pytest.raises(ValidationError) as exc_info:
            parse_payload(invalid_payload_dict)

        assert "file_mtime" in str(exc_info.value)

    def test_both_ranges_set_validation_error(self):
        """Test that having both page_range and line_range raises ValidationError."""
        with pytest.raises(ValueError) as exc_info:
            QdrantPayload(
                file_path="/home/user/test.txt",
                file_mtime=1640995200.0,
                chunk_index=5,
                chunks_total=10,
                chunk_text="This is test chunk content.",
                version="v1.0.0",
                page_range=[1, 3],  # Both ranges set
                line_range=[10, 15]  # Both ranges set
            )

        assert "cannot have both" in str(exc_info.value).lower()

    def test_extra_field_forbidden_via_parse_payload(self):
        """Test that extra fields are forbidden when parsing."""
        invalid_payload_dict = {
            "file_path": "/home/user/test.txt",
            "file_mtime": 1640995200.0,
            "chunk_index": 5,
            "chunks_total": 10,
            "chunk_text": "This is test chunk content.",
            "extra_field": "should_not_be_allowed"
        }

        with pytest.raises(ValidationError) as exc_info:
            parse_payload(invalid_payload_dict)

        assert "extra_field" in str(exc_info.value) or "Extra inputs are not permitted" in str(exc_info.value)

    def test_model_dump(self):
        """Test that model_dump produces correct dictionary."""
        payload = QdrantPayload(
            file_path="/home/user/test.txt",
            file_mtime=1640995200.0,
            chunk_index=5,
            chunks_total=10,
            chunk_text="This is test chunk content.",
            version="v1.0.0",
            page_range=[1, 3],
            line_range=None
        )
        result = payload.model_dump()
        expected = {
            "file_path": "/home/user/test.txt",
            "file_mtime": 1640995200.0,
            "chunk_index": 5,
            "chunks_total": 10,
            "chunk_text": "This is test chunk content.",
            "version": "v1.0.0",
            "page_range": [1, 3],
            "line_range": None
        }

        assert result == expected

    def test_single_page_range(self):
        """Test payload with single-page range."""
        payload = QdrantPayload(
            file_path="/home/user/test.txt",
            file_mtime=1640995200.0,
            chunk_index=5,
            chunks_total=10,
            chunk_text="This is test chunk content.",
            version="v1.0.0",
            page_range=[5],  # Single page
            line_range=None
        )

        assert payload.page_range == [5]

    def test_single_line_range(self):
        """Test payload with single-line range."""
        payload = QdrantPayload(
            file_path="/home/user/test.txt",
            file_mtime=1640995200.0,
            chunk_index=5,
            chunks_total=10,
            chunk_text="This is test chunk content.",
            version="v1.0.0",
            page_range=None,
            line_range=[42]  # Single line
        )

        assert payload.line_range == [42]

    def test_empty_ranges_normalized_to_none(self):
        """Test that empty ranges are normalized to None."""
        payload = QdrantPayload(
            file_path="/home/user/test.txt",
            file_mtime=1640995200.0,
            chunk_index=5,
            chunks_total=10,
            chunk_text="This is test chunk content.",
            version="v1.0.0",
            page_range=[],  # Empty list should become None
            line_range=None
        )

        assert payload.page_range is None  # Should be normalized from []
        assert payload.line_range is None

    def test_empty_line_range_normalized_to_none(self):
        """Test that empty line_range is normalized to None."""
        payload = QdrantPayload(
            file_path="/home/user/test.txt",
            file_mtime=1640995200.0,
            chunk_index=5,
            chunks_total=10,
            chunk_text="This is test chunk content.",
            version="v1.0.0",
            page_range=None,
            line_range=[]  # Empty list should become None
        )

        assert payload.page_range is None
        assert payload.line_range is None  # Should be normalized from []


class TestParsePayload:
    """Test suite for parse_payload function."""

    def test_parse_valid_payload_dict(self):
        """Test parsing a valid payload dictionary."""
        payload_dict = {
            "file_path": "/home/user/test.txt",
            "file_mtime": 1640995200.0,
            "chunk_index": 5,
            "chunks_total": 10,
            "chunk_text": "This is test chunk content.",
            "version": "v1.0.0",
            "page_range": [1, 3],
            "line_range": None
        }

        payload = parse_payload(payload_dict)

        assert isinstance(payload, QdrantPayload)
        assert payload.file_path == "/home/user/test.txt"
        assert payload.file_mtime == 1640995200.0
        assert payload.chunk_index == 5
        assert payload.chunks_total == 10
        assert payload.chunk_text == "This is test chunk content."
        assert payload.version == "v1.0.0"
        assert payload.page_range == [1, 3]
        assert payload.line_range is None

    def test_parse_legacy_payload_without_optional_fields(self):
        """Test parsing legacy payload without optional fields (backward compatibility)."""
        legacy_payload_dict = {
            "file_path": "/home/user/test.txt",
            "file_mtime": 1640995200.0,
            "chunk_index": 5,
            "chunks_total": 10,
            "chunk_text": "This is test chunk content."
            # Missing: version, page_range, line_range
        }

        payload = parse_payload(legacy_payload_dict)

        assert isinstance(payload, QdrantPayload)
        assert payload.file_path == "/home/user/test.txt"
        assert payload.file_mtime == 1640995200.0
        assert payload.chunk_index == 5
        assert payload.chunks_total == 10
        assert payload.chunk_text == "This is test chunk content."
        assert payload.version is None  # Should default to None
        assert payload.page_range is None  # Should default to None
        assert payload.line_range is None  # Should default to None

    def test_parse_payload_with_only_page_range(self):
        """Test parsing payload with only page_range (typical pre-v5.0.0 with pages)."""
        payload_dict = {
            "file_path": "/home/user/test.pdf",
            "file_mtime": 1640995200.0,
            "chunk_index": 2,
            "chunks_total": 5,
            "chunk_text": "PDF chunk content.",
            "version": "v5.1.0",
            "page_range": [10, 12]
            # Missing: line_range (should be None)
        }

        payload = parse_payload(payload_dict)

        assert payload.page_range == [10, 12]
        assert payload.line_range is None

    def test_parse_payload_with_only_line_range(self):
        """Test parsing payload with only line_range (typical pre-v5.0.0 with lines)."""
        payload_dict = {
            "file_path": "/home/user/test.txt",
            "file_mtime": 1640995200.0,
            "chunk_index": 2,
            "chunks_total": 5,
            "chunk_text": "Text chunk content.",
            "version": "v5.1.0",
            "line_range": [20, 25]
            # Missing: page_range (should be None)
        }

        payload = parse_payload(payload_dict)

        assert payload.line_range == [20, 25]
        assert payload.page_range is None

    def test_parse_none_payload_raises_value_error(self):
        """Test that parsing None payload raises ValueError."""
        with pytest.raises(ValueError) as exc_info:
            parse_payload(None)

        assert "cannot be None" in str(exc_info.value)

    def test_parse_invalid_payload_missing_mandatory_field(self):
        """Test that parsing payload missing mandatory field raises ValidationError."""
        invalid_payload_dict = {
            "file_mtime": 1640995200.0,
            "chunk_index": 5,
            "chunks_total": 10,
            "chunk_text": "This is test chunk content."
            # Missing: file_path (mandatory)
        }

        with pytest.raises(ValidationError) as exc_info:
            parse_payload(invalid_payload_dict)

        assert "file_path" in str(exc_info.value)

    def test_parse_invalid_payload_wrong_type(self):
        """Test that parsing payload with wrong type raises ValidationError."""
        invalid_payload_dict = {
            "file_path": "/home/user/test.txt",
            "file_mtime": 1640995200.0,
            "chunk_index": "not_an_int",  # Wrong type
            "chunks_total": 10,
            "chunk_text": "This is test chunk content."
        }

        with pytest.raises(ValidationError) as exc_info:
            parse_payload(invalid_payload_dict)

        assert "chunk_index" in str(exc_info.value)

    def test_parse_payload_with_both_ranges_raises_error(self):
        """Test that parsing payload with both ranges raises ValidationError."""
        invalid_payload_dict = {
            "file_path": "/home/user/test.txt",
            "file_mtime": 1640995200.0,
            "chunk_index": 5,
            "chunks_total": 10,
            "chunk_text": "This is test chunk content.",
            "version": "v1.0.0",
            "page_range": [1, 3],  # Both ranges set
            "line_range": [10, 15]  # Both ranges set
        }

        with pytest.raises(ValidationError) as exc_info:
            parse_payload(invalid_payload_dict)

        assert "cannot have both" in str(exc_info.value).lower()

    def test_parse_payload_with_extra_field_raises_error(self):
        """Test that parsing payload with extra field raises ValidationError."""
        invalid_payload_dict = {
            "file_path": "/home/user/test.txt",
            "file_mtime": 1640995200.0,
            "chunk_index": 5,
            "chunks_total": 10,
            "chunk_text": "This is test chunk content.",
            "extra_field": "should_not_be_allowed"
        }

        with pytest.raises(ValidationError) as exc_info:
            parse_payload(invalid_payload_dict)
        # The error message might vary between Pydantic versions
        assert "extra_field" in str(exc_info.value) or "Extra inputs are not permitted" in str(exc_info.value)

    def test_parse_payload_roundtrip(self):
        """Test that parsing a model_dump produces equivalent model."""
        original_payload = QdrantPayload(
            file_path="/home/user/test.txt",
            file_mtime=1640995200.0,
            chunk_index=5,
            chunks_total=10,
            chunk_text="This is test chunk content.",
            version="v1.0.0",
            page_range=[1, 3],
            line_range=None
        )

        payload_dict = original_payload.model_dump()
        parsed_payload = parse_payload(payload_dict)

        assert parsed_payload == original_payload
        assert parsed_payload.file_path == original_payload.file_path
        assert parsed_payload.file_mtime == original_payload.file_mtime
        assert parsed_payload.chunk_index == original_payload.chunk_index
        assert parsed_payload.chunks_total == original_payload.chunks_total
        assert parsed_payload.chunk_text == original_payload.chunk_text
        assert parsed_payload.version == original_payload.version
        assert parsed_payload.page_range == original_payload.page_range
        assert parsed_payload.line_range == original_payload.line_range
