"""Unit tests for CacheManager.

This module contains comprehensive tests for the cache manager functionality,
including JSON and pickle serialization, Pydantic models, and dataclasses.
"""

import tempfile
from dataclasses import dataclass
from pathlib import Path

from pydantic import BaseModel

from hezor_common.utilities.cache_manager import CacheManager, CacheType


class SamplePydanticModel(BaseModel):
    """Sample Pydantic model for testing."""

    name: str
    value: int
    tags: list[str] = []


@dataclass
class SampleDataclass:
    """Sample dataclass for testing."""

    name: str
    value: int
    tags: list[str]


class TestCacheManagerInitialization:
    """Tests for CacheManager initialization."""

    def test_init_with_default_base_dir(self):
        """Test initialization with default base directory."""
        manager = CacheManager[dict](CacheType.SECTION)

        assert manager.cache_type == CacheType.SECTION
        assert manager.cache_dir.exists()
        assert manager.cache_dir.name == "serialized_sections"
        assert not manager.use_json

    def test_init_with_custom_base_dir(self):
        """Test initialization with custom base directory."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.CREATION, base_dir=tmpdir)

            assert manager.base_dir == Path(tmpdir)
            assert manager.cache_dir == Path(tmpdir) / "serialized_creations"
            assert manager.cache_dir.exists()

    def test_init_with_pydantic_model(self):
        """Test initialization with Pydantic model class."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[SamplePydanticModel](
                CacheType.GENERIC, base_dir=tmpdir, model_class=SamplePydanticModel
            )

            assert manager.model_class is SamplePydanticModel
            assert manager.use_json is True

    def test_init_with_dataclass(self):
        """Test initialization with dataclass."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[SampleDataclass](
                CacheType.GENERIC, base_dir=tmpdir, model_class=SampleDataclass
            )

            assert manager.model_class is SampleDataclass
            assert manager.use_json is True


class TestCacheManagerFileOperations:
    """Tests for file operations."""

    def test_get_extension(self):
        """Test getting correct file extensions."""
        with tempfile.TemporaryDirectory() as tmpdir:
            test_cases = [
                (CacheType.CREATION, ".creation"),
                (CacheType.CHAPTER, ".chapter"),
                (CacheType.SECTION, ".section"),
                (CacheType.EXECUTION, ".exec"),
                (CacheType.AGENT_GENERATION, ".gen"),
                (CacheType.HTTP_RESPONSES, ".http"),
                (CacheType.GENERIC, ".cache"),
            ]

            for cache_type, expected_ext in test_cases:
                manager = CacheManager[dict](cache_type, base_dir=tmpdir)
                assert manager._get_extension() == expected_ext

    def test_build_filename(self):
        """Test filename building."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.SECTION, base_dir=tmpdir)
            filename = manager._build_filename("section_123", "abc456hash")

            assert filename == "section_123__abc456hash.section"

    def test_get_cache_path(self):
        """Test getting cache file path."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.CHAPTER, base_dir=tmpdir)
            cache_path = manager.get_cache_path("chapter_456", "def789hash")

            expected_path = Path(tmpdir) / "serialized_chapters" / "chapter_456__def789hash.chapter"
            assert cache_path == expected_path


class TestCacheManagerBasicOperations:
    """Tests for basic cache operations."""

    def test_save_and_get_dict_pickle(self):
        """Test saving and getting dictionary with pickle."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.GENERIC, base_dir=tmpdir)

            test_data = {"name": "test", "value": 42, "items": [1, 2, 3]}
            content_hash = "test_hash_123"

            # Save
            cache_path = manager.save("test_id", content_hash, test_data)
            assert cache_path.exists()

            # Get
            retrieved = manager.get(content_hash)
            assert retrieved == test_data

    def test_save_and_get_pydantic_model(self):
        """Test saving and getting Pydantic model with JSON."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[SamplePydanticModel](
                CacheType.GENERIC, base_dir=tmpdir, model_class=SamplePydanticModel
            )

            test_model = SamplePydanticModel(name="test", value=42, tags=["a", "b"])
            content_hash = "pydantic_hash_456"

            # Save
            cache_path = manager.save("model_id", content_hash, test_model)
            assert cache_path.exists()

            # Get
            retrieved = manager.get(content_hash)
            assert isinstance(retrieved, SamplePydanticModel)
            assert retrieved.name == "test"
            assert retrieved.value == 42
            assert retrieved.tags == ["a", "b"]

    def test_save_and_get_dataclass(self):
        """Test saving and getting dataclass with JSON."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[SampleDataclass](
                CacheType.GENERIC, base_dir=tmpdir, model_class=SampleDataclass
            )

            test_data = SampleDataclass(name="test", value=42, tags=["x", "y"])
            content_hash = "dataclass_hash_789"

            # Save
            cache_path = manager.save("data_id", content_hash, test_data)
            assert cache_path.exists()

            # Get
            retrieved = manager.get(content_hash)
            assert isinstance(retrieved, SampleDataclass)
            assert retrieved.name == "test"
            assert retrieved.value == 42
            assert retrieved.tags == ["x", "y"]

    def test_exists(self):
        """Test checking if cache exists."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.GENERIC, base_dir=tmpdir)

            content_hash = "exists_test_hash"

            # Should not exist initially
            assert not manager.exists(content_hash)

            # Save and check again
            manager.save("test_id", content_hash, {"data": "test"})
            assert manager.exists(content_hash)

    def test_get_nonexistent_cache(self):
        """Test getting non-existent cache returns None."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.GENERIC, base_dir=tmpdir)

            result = manager.get("nonexistent_hash")
            assert result is None

    def test_delete_cache(self):
        """Test deleting cached file."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.GENERIC, base_dir=tmpdir)

            content_hash = "delete_test_hash"
            manager.save("test_id", content_hash, {"data": "test"})

            # Verify it exists
            assert manager.exists(content_hash)

            # Delete it
            deleted = manager.delete(content_hash)
            assert deleted is True
            assert not manager.exists(content_hash)

            # Try deleting again
            deleted_again = manager.delete(content_hash)
            assert deleted_again is False

    def test_list_all(self):
        """Test listing all cached files."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.GENERIC, base_dir=tmpdir)

            # Initially empty
            assert len(manager.list_all()) == 0

            # Save multiple items
            manager.save("id1", "hash1", {"data": 1})
            manager.save("id2", "hash2", {"data": 2})
            manager.save("id3", "hash3", {"data": 3})

            # List all
            all_files = manager.list_all()
            assert len(all_files) == 3

    def test_clear_all(self):
        """Test clearing all cached files."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.GENERIC, base_dir=tmpdir)

            # Save multiple items
            manager.save("id1", "hash1", {"data": 1})
            manager.save("id2", "hash2", {"data": 2})
            manager.save("id3", "hash3", {"data": 3})

            # Clear all
            count = manager.clear_all()
            assert count == 3
            assert len(manager.list_all()) == 0


class TestCacheManagerHashLookup:
    """Tests for hash-based lookup functionality."""

    def test_find_by_hash(self):
        """Test finding cache file by hash only."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.GENERIC, base_dir=tmpdir)

            content_hash = "find_test_hash"
            manager.save("some_identifier", content_hash, {"data": "test"})

            # Find by hash regardless of identifier
            found_path = manager._find_by_hash(content_hash)
            assert found_path is not None
            assert content_hash in found_path.name

    def test_multiple_identifiers_same_hash(self):
        """Test that only one file exists for same hash with different identifiers."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.GENERIC, base_dir=tmpdir)

            content_hash = "shared_hash"
            test_data = {"data": "test"}

            # Save with first identifier
            manager.save("id1", content_hash, test_data)

            # Save with different identifier but same hash (should overwrite)
            manager.save("id2", content_hash, test_data)

            # Should still find it
            assert manager.exists(content_hash)
            retrieved = manager.get(content_hash)
            assert retrieved == test_data


class TestCacheManagerEdgeCases:
    """Tests for edge cases and error handling."""

    def test_get_corrupted_cache_returns_none(self):
        """Test that corrupted cache file returns None."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.GENERIC, base_dir=tmpdir)

            content_hash = "corrupted_hash"
            cache_path = manager.get_cache_path("test_id", content_hash)

            # Write corrupted data
            cache_path.write_bytes(b"corrupted binary data that's not pickle")

            # Should return None instead of raising
            result = manager.get(content_hash)
            assert result is None

    def test_save_complex_nested_structure(self):
        """Test saving complex nested data structures."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[dict](CacheType.GENERIC, base_dir=tmpdir)

            complex_data = {
                "level1": {
                    "level2": {
                        "level3": {
                            "values": [1, 2, 3],
                            "nested_list": [[1, 2], [3, 4]],
                        }
                    },
                    "items": ["a", "b", "c"],
                },
                "metadata": {"count": 100, "active": True},
            }

            content_hash = "complex_hash"
            manager.save("complex_id", content_hash, complex_data)

            retrieved = manager.get(content_hash)
            assert retrieved == complex_data

    def test_json_fallback_to_pickle(self):
        """Test that non-JSON-serializable objects fallback to pickle."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create manager with model_class to trigger JSON attempt
            manager = CacheManager[dict](CacheType.GENERIC, base_dir=tmpdir, model_class=dict)

            # Create data with non-JSON-serializable object (set)
            test_data = {"regular": "data", "set": {1, 2, 3}}

            content_hash = "fallback_hash"
            cache_path = manager.save("test_id", content_hash, test_data)

            # Should have saved successfully (using pickle fallback)
            assert cache_path.exists()

            # Should be able to retrieve (note: set will be preserved via pickle)
            retrieved = manager.get(content_hash)
            assert retrieved is not None
            assert retrieved["regular"] == "data"


class TestCacheManagerIntegration:
    """Integration tests for complete workflows."""

    def test_complete_workflow_with_pydantic(self):
        """Test complete workflow: save, check, get, delete."""
        with tempfile.TemporaryDirectory() as tmpdir:
            manager = CacheManager[SamplePydanticModel](
                CacheType.AGENT_GENERATION, base_dir=tmpdir, model_class=SamplePydanticModel
            )

            # Create test model
            model = SamplePydanticModel(
                name="workflow_test", value=999, tags=["test", "integration"]
            )

            content_hash = "workflow_hash"

            # 1. Check it doesn't exist
            assert not manager.exists(content_hash)

            # 2. Save it
            cache_path = manager.save("workflow_id", content_hash, model)
            assert cache_path.exists()

            # 3. Check it exists
            assert manager.exists(content_hash)

            # 4. Get it
            retrieved = manager.get(content_hash)
            assert retrieved is not None
            assert retrieved.name == "workflow_test"
            assert retrieved.value == 999

            # 5. List all (should have 1)
            assert len(manager.list_all()) == 1

            # 6. Delete it
            assert manager.delete(content_hash)
            assert not manager.exists(content_hash)

            # 7. List all (should be empty)
            assert len(manager.list_all()) == 0
