#!/usr/bin/env python3
"""
Comprehensive Storage Functional Test Suite
============================================

Tests all storage features based on actual module APIs:

1. Storage Backends (Filesystem, Memory)
2. Versioning (VersioningMode enum, VersionInfo)
3. Retention Policies (RetentionPolicy protocol)
4. Caching (InMemoryCache, LRUCache, LFUCache, TTLCache with CacheConfig)
5. Batching Operations (BatchConfig, MemoryAwareBuffer, BatchWriter)
6. Encryption (AesGcmEncryptor, FernetEncryptor, ChaCha20Poly1305Encryptor)
7. Compression (GzipCompressor, ZstdCompressor, AdaptiveCompressor)
8. Concurrency Control (LockStrategy, AtomicFileWriter)
9. Backpressure Strategies (MemoryBasedBackpressure, TokenBucketBackpressure, CircuitBreaker)
10. Tiering (TierType, TierInfo, TieringConfig)
11. Replication (ReplicationMode, ReplicaHealth)
12. Schema Migration (SchemaVersion, MigrationInfo)
13. Streaming Storage (StreamingConfig, StreamingFormat)
14. Observability (AuditLogger, StoreMetrics, Tracer)
"""

import asyncio
import json
import os
import sys
import tempfile
import time
import threading
from dataclasses import dataclass, field
from datetime import datetime, timedelta, UTC
from pathlib import Path
from typing import Any

# Add source to path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))


@dataclass
class TestResult:
    """Individual test result."""
    name: str
    passed: bool
    duration: float
    error: str | None = None
    details: dict[str, Any] = field(default_factory=dict)


@dataclass
class TestCategory:
    """Test category results."""
    name: str
    results: list[TestResult] = field(default_factory=list)

    @property
    def passed(self) -> int:
        return sum(1 for r in self.results if r.passed)

    @property
    def failed(self) -> int:
        return sum(1 for r in self.results if not r.passed)

    @property
    def total(self) -> int:
        return len(self.results)


class StorageFunctionalTests:
    """Comprehensive storage functional test suite."""

    def __init__(self):
        self.categories: list[TestCategory] = []
        self.temp_dir: Path | None = None

    def setup(self):
        """Setup test environment."""
        self.temp_dir = Path(tempfile.mkdtemp(prefix="truthound_storage_test_"))
        print(f"\n📁 Test directory: {self.temp_dir}")

    def teardown(self):
        """Cleanup test environment."""
        if self.temp_dir and self.temp_dir.exists():
            import shutil
            shutil.rmtree(self.temp_dir, ignore_errors=True)
            print(f"\n🧹 Cleaned up: {self.temp_dir}")

    def run_test(self, name: str, test_func) -> TestResult:
        """Run a single test and capture result."""
        start = time.time()
        try:
            details = test_func()
            duration = time.time() - start
            return TestResult(name=name, passed=True, duration=duration, details=details or {})
        except Exception as e:
            duration = time.time() - start
            import traceback
            return TestResult(
                name=name,
                passed=False,
                duration=duration,
                error=f"{type(e).__name__}: {e}",
                details={"traceback": traceback.format_exc()}
            )

    def run_category(self, category_name: str, tests: list[tuple[str, callable]]):
        """Run all tests in a category."""
        print(f"\n{'='*60}")
        print(f"📦 {category_name}")
        print(f"{'='*60}")

        category = TestCategory(name=category_name)

        for test_name, test_func in tests:
            result = self.run_test(test_name, test_func)
            category.results.append(result)

            status = "✅" if result.passed else "❌"
            print(f"  {status} {test_name} ({result.duration:.3f}s)")
            if result.error:
                print(f"     └─ {result.error}")

        self.categories.append(category)
        return category

    # ==========================================================================
    # 1. STORAGE BACKENDS
    # ==========================================================================

    def test_filesystem_backend(self) -> dict:
        """Test filesystem storage backend."""
        from truthound.stores import get_store, ValidationResult, ValidatorResult, ResultStatus
        from truthound.stores.results import ResultStatistics

        store = get_store("filesystem", base_path=str(self.temp_dir / "fs_store"))

        # Create a validation result with correct API
        result = ValidationResult(
            run_id="test-run-001",
            run_time=datetime.now(UTC),
            data_asset="customers.csv",
            status=ResultStatus.SUCCESS,
            results=[
                ValidatorResult(
                    validator_name="not_null",
                    success=True,
                    column="id",
                    count=0,
                )
            ],
            statistics=ResultStatistics(total_rows=1000, validated_rows=1000),
            metadata={"source": "test"}
        )

        # Save
        run_id = store.save(result)
        assert run_id == "test-run-001", f"Expected run_id 'test-run-001', got '{run_id}'"

        # Get
        retrieved = store.get(run_id)
        assert retrieved is not None, "Failed to retrieve result"
        assert retrieved.data_asset == "customers.csv"

        # List
        runs = store.list_runs("customers.csv")
        assert run_id in runs, "Run ID not in list"

        # Query
        query_results = store.query(data_asset="customers.csv")
        assert len(query_results) >= 1

        # Delete
        deleted = store.delete(run_id)
        assert deleted, "Failed to delete result"

        # Verify deletion
        assert store.get(run_id) is None, "Result still exists after deletion"

        return {"operations": ["save", "get", "list", "query", "delete"]}

    def test_memory_backend(self) -> dict:
        """Test in-memory storage backend."""
        from truthound.stores import get_store, ValidationResult, ValidatorResult, ResultStatus
        from truthound.stores.results import ResultStatistics

        store = get_store("memory")

        # Create and save multiple results
        results_saved = 0
        for i in range(5):
            result = ValidationResult(
                run_id=f"mem-run-{i:03d}",
                run_time=datetime.now(UTC),
                data_asset=f"data_{i % 2}.csv",
                status=ResultStatus.SUCCESS if i % 2 == 0 else ResultStatus.FAILURE,
                results=[],
                statistics=ResultStatistics(total_rows=100, validated_rows=100),
            )
            store.save(result)
            results_saved += 1

        # Query by data asset
        all_runs = store.list_runs()
        successful = [r for r in all_runs if store.get(r).status == ResultStatus.SUCCESS]

        # Clear store
        store.clear()
        assert len(store.list_runs()) == 0, "Store not cleared"

        return {"saved": results_saved, "successful": len(successful)}

    # ==========================================================================
    # 2. VERSIONING
    # ==========================================================================

    def test_versioning_modes(self) -> dict:
        """Test versioning modes enum."""
        from truthound.stores.versioning.base import VersioningMode, VersionInfo, VersionDiff

        # Test all modes
        modes = list(VersioningMode)
        assert VersioningMode.INCREMENTAL in modes
        assert VersioningMode.SEMANTIC in modes
        assert VersioningMode.TIMESTAMP in modes
        assert VersioningMode.GIT_LIKE in modes

        # Test VersionInfo
        info = VersionInfo(
            version=1,
            item_id="test-item",
            created_at=datetime.now(UTC),
            message="Initial version",
        )
        info_dict = info.to_dict()
        assert info_dict["version"] == 1
        assert info_dict["item_id"] == "test-item"

        # Test round-trip
        info2 = VersionInfo.from_dict(info_dict)
        assert info2.version == info.version
        assert info2.item_id == info.item_id

        return {"modes": [m.value for m in modes], "info_created": True}

    def test_versioning_config(self) -> dict:
        """Test versioning configuration."""
        from truthound.stores.versioning.base import VersioningConfig, VersioningMode

        # Test with default values - VersioningConfig is a dataclass
        from truthound.stores.versioning.store import VersionedStore
        from truthound.stores import get_store

        base_store = get_store("memory")

        # Create versioned store wrapper
        versioned = VersionedStore(
            store=base_store,
            mode=VersioningMode.INCREMENTAL,
        )

        return {"mode": VersioningMode.INCREMENTAL.value}

    # ==========================================================================
    # 3. RETENTION POLICIES
    # ==========================================================================

    def test_retention_config(self) -> dict:
        """Test retention configuration."""
        from truthound.stores.retention.base import (
            RetentionConfig,
            RetentionAction,
            RetentionPolicy,
            RetentionResult,
            ItemMetadata,
        )

        # Test config
        config = RetentionConfig(
            enabled=True,
            default_action=RetentionAction.DELETE,
        )
        assert config.enabled

        # Test ItemMetadata
        metadata = ItemMetadata(
            item_id="test-001",
            created_at=datetime.now(UTC),
            size_bytes=1000,
        )
        assert metadata.item_id == "test-001"

        return {"config_created": True, "actions": [a.value for a in RetentionAction]}

    def test_retention_policies(self) -> dict:
        """Test retention policy implementations."""
        from truthound.stores.retention.policies import (
            TimeBasedPolicy,
            CountBasedPolicy,
            SizeBasedPolicy,
        )
        from truthound.stores.retention.base import RetentionAction, ItemMetadata

        now = datetime.now(UTC)

        # Time-based policy
        time_policy = TimeBasedPolicy(
            max_age=timedelta(days=7),
            action=RetentionAction.DELETE,
        )

        old_item = ItemMetadata(
            item_id="old-001",
            created_at=now - timedelta(days=10),
            size_bytes=100,
        )
        new_item = ItemMetadata(
            item_id="new-001",
            created_at=now - timedelta(days=1),
            size_bytes=100,
        )

        old_result = time_policy.evaluate(old_item)
        new_result = time_policy.evaluate(new_item)

        assert old_result.should_apply, "Old item should be marked for action"
        assert not new_result.should_apply, "New item should not be marked"

        # Count-based policy
        count_policy = CountBasedPolicy(max_count=3, action=RetentionAction.ARCHIVE)

        # Size-based policy
        size_policy = SizeBasedPolicy(max_size_bytes=10000, action=RetentionAction.COMPRESS)

        return {
            "time_policy_works": True,
            "policies_tested": ["time", "count", "size"]
        }

    # ==========================================================================
    # 4. CACHING
    # ==========================================================================

    def test_caching_inmemory(self) -> dict:
        """Test in-memory cache backend."""
        from truthound.stores.caching.backends import InMemoryCache
        from truthound.stores.caching.base import CacheConfig

        config = CacheConfig(max_size=100, ttl_seconds=60)
        cache = InMemoryCache(config)

        # Set and get
        cache.set("key1", {"data": "value1"})
        result = cache.get("key1")
        assert result == {"data": "value1"}

        # Exists
        assert cache.exists("key1")
        assert not cache.exists("nonexistent")

        # Delete
        cache.delete("key1")
        assert not cache.exists("key1")

        # Batch operations
        cache.set_many({"a": 1, "b": 2, "c": 3})
        results = cache.get_many(["a", "b", "c", "d"])
        assert results["a"] == 1
        assert results["d"] is None

        # Metrics
        metrics = cache.get_metrics()

        return {"size": cache.size, "hits": metrics.hits, "misses": metrics.misses}

    def test_caching_lru(self) -> dict:
        """Test LRU cache eviction."""
        from truthound.stores.caching.backends import LRUCache
        from truthound.stores.caching.base import CacheConfig

        config = CacheConfig(max_size=3)
        cache = LRUCache(config)

        # Fill cache
        cache.set("a", 1)
        cache.set("b", 2)
        cache.set("c", 3)

        # Access 'a' to make it recently used
        cache.get("a")

        # Add new item - should evict 'b' (least recently used)
        cache.set("d", 4)

        assert cache.exists("a"), "'a' should exist (recently accessed)"
        assert not cache.exists("b"), "'b' should be evicted (LRU)"
        assert cache.exists("c"), "'c' should exist"
        assert cache.exists("d"), "'d' should exist"

        return {"evicted": "b", "remaining": ["a", "c", "d"]}

    def test_caching_lfu(self) -> dict:
        """Test LFU cache eviction."""
        from truthound.stores.caching.backends import LFUCache
        from truthound.stores.caching.base import CacheConfig

        config = CacheConfig(max_size=3)
        cache = LFUCache(config)

        # Fill cache with different access frequencies
        cache.set("a", 1)
        cache.set("b", 2)
        cache.set("c", 3)

        # Access 'a' multiple times
        cache.get("a")
        cache.get("a")
        cache.get("a")

        # Access 'b' once
        cache.get("b")

        # 'c' not accessed - should be evicted first
        cache.set("d", 4)

        assert cache.exists("a"), "'a' should exist (high frequency)"
        assert cache.exists("b"), "'b' should exist"
        assert not cache.exists("c"), "'c' should be evicted (lowest frequency)"

        return {"evicted": "c", "a_frequency": 3}

    def test_caching_ttl(self) -> dict:
        """Test TTL-based cache expiration."""
        from truthound.stores.caching.backends import TTLCache
        from truthound.stores.caching.base import CacheConfig

        config = CacheConfig(ttl_seconds=0.5)  # 500ms TTL
        cache = TTLCache(config)

        cache.set("key1", "value1")

        # Should exist immediately
        assert cache.get("key1") == "value1"

        # Wait for expiration
        time.sleep(0.6)

        # Should be expired
        assert cache.get("key1") is None

        return {"ttl": 0.5, "expired_after": 0.6}

    # ==========================================================================
    # 5. BATCHING
    # ==========================================================================

    def test_batching_buffer(self) -> dict:
        """Test batch buffer operations."""
        from truthound.stores.batching.buffer import BatchBuffer, MemoryAwareBuffer

        # Basic buffer
        buffer = BatchBuffer(max_size=5)

        for i in range(3):
            buffer.add(f"item_{i}")

        assert len(buffer) == 3
        assert not buffer.is_full

        # Drain items
        drained = buffer.drain(2)
        assert len(drained) == 2
        assert len(buffer) == 1

        # Memory-aware buffer (correct parameter name: max_memory_mb)
        mem_buffer = MemoryAwareBuffer(max_size=100, max_memory_mb=1.0)

        for i in range(10):
            mem_buffer.add({"index": i, "data": "x" * 50})

        stats = mem_buffer.get_stats()

        return {
            "basic_buffer_size": len(buffer),
            "memory_buffer_size": stats.get("item_count", 0),
            "memory_used_bytes": stats.get("memory_used", 0),
        }

    def test_batching_writer(self) -> dict:
        """Test batch writer operations."""
        from truthound.stores.batching.writer import BatchWriter
        from truthound.stores.batching.base import BatchConfig

        written_batches = []

        def write_func(batch):
            written_batches.append(list(batch))
            return True

        config = BatchConfig(batch_size=3, flush_interval_seconds=60)  # Long interval for manual control
        writer = BatchWriter(write_func=write_func, config=config)

        # Add items
        for i in range(7):
            writer.add(f"item_{i}")

        # Manual flush
        writer.flush()

        # Should have created batches
        total_items = sum(len(b) for b in written_batches)

        return {"batches_created": len(written_batches), "total_items": total_items}

    # ==========================================================================
    # 6. ENCRYPTION
    # ==========================================================================

    def test_encryption_aes_gcm(self) -> dict:
        """Test AES-GCM encryption."""
        from truthound.stores.encryption.providers import AesGcmEncryptor
        from truthound.stores.encryption.base import generate_key, EncryptionAlgorithm

        key = generate_key(EncryptionAlgorithm.AES_256_GCM)
        encryptor = AesGcmEncryptor()

        original_data = b"Sensitive validation data - customers PII"

        # Encrypt
        encrypted = encryptor.encrypt(original_data, key)
        assert encrypted != original_data
        assert len(encrypted) > len(original_data)  # IV + tag overhead

        # Decrypt
        decrypted = encryptor.decrypt(encrypted, key)
        assert decrypted == original_data

        return {
            "original_size": len(original_data),
            "encrypted_size": len(encrypted),
            "decrypted_match": decrypted == original_data,
        }

    def test_encryption_fernet(self) -> dict:
        """Test Fernet encryption."""
        from truthound.stores.encryption.providers import FernetEncryptor

        encryptor = FernetEncryptor()
        key = encryptor.generate_key()

        original_data = b"Another sensitive message for Fernet encryption"

        encrypted = encryptor.encrypt(original_data, key)
        decrypted = encryptor.decrypt(encrypted, key)

        assert decrypted == original_data

        return {"algorithm": "fernet", "success": True}

    def test_encryption_wrong_key(self) -> dict:
        """Test that wrong key fails decryption."""
        from truthound.stores.encryption.providers import AesGcmEncryptor
        from truthound.stores.encryption.base import (
            generate_key,
            EncryptionAlgorithm,
            DecryptionError,
        )

        encryptor = AesGcmEncryptor()
        key1 = generate_key(EncryptionAlgorithm.AES_256_GCM)
        key2 = generate_key(EncryptionAlgorithm.AES_256_GCM)

        data = b"Secret data"
        encrypted = encryptor.encrypt(data, key1)

        # Try to decrypt with wrong key
        decryption_failed = False
        try:
            encryptor.decrypt(encrypted, key2)
        except Exception:  # Could be DecryptionError or InvalidTag
            decryption_failed = True

        assert decryption_failed, "Decryption with wrong key should fail"

        return {"wrong_key_rejected": True}

    # ==========================================================================
    # 7. COMPRESSION
    # ==========================================================================

    def test_compression_gzip(self) -> dict:
        """Test gzip compression."""
        from truthound.stores.compression.providers import GzipCompressor

        compressor = GzipCompressor()

        # Highly compressible data
        original = b"AAAAAAAAAA" * 1000  # 10KB of repeated data

        compressed = compressor.compress(original)
        decompressed = compressor.decompress(compressed)

        ratio = len(compressed) / len(original)

        assert decompressed == original
        assert ratio < 0.1  # Should compress well

        return {
            "original_size": len(original),
            "compressed_size": len(compressed),
            "ratio": f"{ratio:.2%}",
        }

    def test_compression_zstd(self) -> dict:
        """Test zstd compression if available."""
        try:
            from truthound.stores.compression.providers import ZstdCompressor

            compressor = ZstdCompressor()

            # JSON-like data
            original = json.dumps({"key": "value" * 100} * 10).encode()

            compressed = compressor.compress(original)
            decompressed = compressor.decompress(compressed)

            ratio = len(compressed) / len(original)

            return {
                "available": True,
                "original_size": len(original),
                "compressed_size": len(compressed),
                "ratio": f"{ratio:.2%}",
            }
        except Exception as e:
            return {"available": False, "reason": str(e)}

    def test_compression_adaptive(self) -> dict:
        """Test adaptive compression algorithm selection."""
        from truthound.stores.compression.adaptive import (
            AdaptiveCompressor,
            DataAnalyzer,
            AdaptiveConfig,
            CompressionGoal,
        )

        # Analyze data characteristics
        analyzer = DataAnalyzer()

        text_data = b"Hello world! " * 100
        binary_data = bytes(range(256)) * 10
        json_data = json.dumps({"key": f"value_{i}" for i in range(100)}).encode()

        text_analysis = analyzer.analyze(text_data)
        binary_analysis = analyzer.analyze(binary_data)
        json_analysis = analyzer.analyze(json_data)

        # Adaptive compressor with config
        config = AdaptiveConfig(goal=CompressionGoal.BALANCED)
        compressor = AdaptiveCompressor(config=config)

        compressed = compressor.compress(json_data)
        decompressed = compressor.decompress(compressed)

        assert decompressed == json_data

        return {
            "text_type": str(text_analysis.data_type),
            "binary_type": str(binary_analysis.data_type),
            "json_type": str(json_analysis.data_type),
        }

    # ==========================================================================
    # 8. CONCURRENCY CONTROL
    # ==========================================================================

    def test_concurrency_lock_strategy(self) -> dict:
        """Test lock strategy selection."""
        from truthound.stores.concurrency.locks import (
            LockStrategy,
            get_default_lock_strategy,
            NoOpLockStrategy,
        )

        # Test strategies exist
        assert LockStrategy.AUTO is not None
        assert LockStrategy.FCNTL is not None
        assert LockStrategy.FILELOCK is not None

        # Get default strategy
        default = get_default_lock_strategy()
        assert default is not None

        # Test NoOpLockStrategy
        noop = NoOpLockStrategy()
        lock = noop.acquire("test_resource", timeout=1.0)
        assert lock is not None
        noop.release("test_resource")

        return {"strategies": [s.name for s in LockStrategy]}

    def test_concurrency_atomic_operations(self) -> dict:
        """Test atomic file operations."""
        from truthound.stores.concurrency.atomic import (
            AtomicFileWriter,
            AtomicFileReader,
            atomic_write,
        )

        test_file = self.temp_dir / "atomic_test.txt"

        # Atomic write using context manager
        writer = AtomicFileWriter(test_file)
        with writer:
            writer.write(b"Hello, atomic world!")

        assert test_file.exists()
        assert test_file.read_bytes() == b"Hello, atomic world!"

        # Test atomic_write function
        atomic_write(test_file, b"Updated content")
        assert test_file.read_bytes() == b"Updated content"

        return {"atomic_write": True, "file_exists": test_file.exists()}

    # ==========================================================================
    # 9. BACKPRESSURE
    # ==========================================================================

    def test_backpressure_memory_based(self) -> dict:
        """Test memory-based backpressure."""
        from truthound.stores.backpressure.strategies import MemoryBasedBackpressure
        from truthound.stores.backpressure.base import PressureLevel, BackpressureConfig

        # Mock memory provider
        current_memory = [0.5]  # 50% usage

        def memory_provider():
            return current_memory[0]

        config = BackpressureConfig()
        bp = MemoryBasedBackpressure(config=config, memory_provider=memory_provider)

        # Check pressure calculation
        pressure = bp.calculate_pressure()
        assert 0 <= pressure <= 1

        # High memory
        current_memory[0] = 0.95
        high_pressure = bp.calculate_pressure()
        assert high_pressure > 0.5

        return {"pressure_calculated": True}

    def test_backpressure_token_bucket(self) -> dict:
        """Test token bucket rate limiting."""
        from truthound.stores.backpressure.strategies import TokenBucketBackpressure
        from truthound.stores.backpressure.base import BackpressureConfig

        config = BackpressureConfig(
            initial_rate=10.0,
            max_rate=20.0,
        )
        tb = TokenBucketBackpressure(config=config, bucket_size=10.0)

        # Try to acquire tokens
        acquired = 0
        for _ in range(15):
            if tb.try_acquire():
                acquired += 1

        # Should be limited by bucket size
        assert acquired <= 10

        return {"initial_acquired": acquired}

    def test_backpressure_circuit_breaker(self) -> dict:
        """Test circuit breaker pattern."""
        from truthound.stores.backpressure.circuit_breaker import (
            CircuitBreaker,
            CircuitBreakerConfig,
        )

        config = CircuitBreakerConfig(
            failure_threshold=3,
            success_threshold=2,
            timeout_seconds=1.0,
        )
        cb = CircuitBreaker(config=config)

        # Initially closed
        assert cb.is_closed

        # Simulate failures
        for _ in range(3):
            cb.record_failure()

        # Should be open now
        assert cb.is_open

        # Calls should be rejected
        assert not cb.allow_request()

        # Wait for timeout
        time.sleep(1.1)

        # Should be half-open
        assert cb.is_half_open or cb.allow_request()

        return {"transitions": ["closed", "open", "half-open"]}

    # ==========================================================================
    # 10. TIERING
    # ==========================================================================

    def test_tiering_types(self) -> dict:
        """Test tiering type enum and info."""
        from truthound.stores.tiering.base import (
            TierType,
            TierInfo,
            StorageTier,
            TieringConfig,
            MigrationDirection,
        )

        # Test tier types
        assert TierType.HOT is not None
        assert TierType.WARM is not None
        assert TierType.COLD is not None
        assert TierType.ARCHIVE is not None

        # Test TierInfo
        info = TierInfo(
            item_id="test-001",
            current_tier=TierType.HOT,
            created_at=datetime.now(UTC),
        )
        assert info.current_tier == TierType.HOT

        # Test StorageTier
        tier = StorageTier(
            tier_type=TierType.HOT,
            name="hot",
            priority=0,
        )
        assert tier.priority == 0

        # Test MigrationDirection
        assert MigrationDirection.DEMOTE is not None
        assert MigrationDirection.PROMOTE is not None

        return {"tiers": [t.value for t in TierType]}

    def test_tiering_policies(self) -> dict:
        """Test tiering policy implementations."""
        from truthound.stores.tiering.policies import (
            AgeBasedTierPolicy,
            AccessBasedTierPolicy,
        )
        from truthound.stores.tiering.base import TierType, TierInfo

        now = datetime.now(UTC)

        # Age-based policy
        age_policy = AgeBasedTierPolicy(
            from_tier=TierType.HOT,
            to_tier=TierType.WARM,
            min_age=timedelta(days=7),
        )

        old_info = TierInfo(
            item_id="old-001",
            current_tier=TierType.HOT,
            created_at=now - timedelta(days=10),
        )

        new_info = TierInfo(
            item_id="new-001",
            current_tier=TierType.HOT,
            created_at=now - timedelta(days=1),
        )

        old_result = age_policy.evaluate(old_info)
        new_result = age_policy.evaluate(new_info)

        assert old_result.should_migrate
        assert old_result.target_tier == TierType.WARM
        assert not new_result.should_migrate

        return {"age_policy_works": True}

    # ==========================================================================
    # 11. REPLICATION
    # ==========================================================================

    def test_replication_modes(self) -> dict:
        """Test replication mode enum and config."""
        from truthound.stores.replication.base import (
            ReplicationMode,
            ReplicationConfig,
            ReplicaHealth,
            ReplicaState,
            ConflictResolution,
        )

        # Test modes
        assert ReplicationMode.SYNC is not None
        assert ReplicationMode.ASYNC is not None
        assert ReplicationMode.SEMI_SYNC is not None

        # Test health states
        assert ReplicaHealth.HEALTHY is not None
        assert ReplicaHealth.DEGRADED is not None
        assert ReplicaHealth.UNHEALTHY is not None

        # Test conflict resolution
        assert ConflictResolution.LAST_WRITE_WINS is not None
        assert ConflictResolution.FIRST_WRITE_WINS is not None

        # Test config
        config = ReplicationConfig(
            mode=ReplicationMode.ASYNC,
            conflict_resolution=ConflictResolution.LAST_WRITE_WINS,
        )
        assert config.mode == ReplicationMode.ASYNC

        return {"modes": [m.value for m in ReplicationMode]}

    # ==========================================================================
    # 12. SCHEMA MIGRATION
    # ==========================================================================

    def test_migration_types(self) -> dict:
        """Test migration type definitions."""
        from truthound.stores.migration.base import (
            SchemaVersion,
            MigrationInfo,
            MigrationConfig,
            MigrationDirection,
        )

        # Test SchemaVersion
        version = SchemaVersion(major=1, minor=2, patch=3)
        assert str(version) == "1.2.3"

        # Compare versions
        v1 = SchemaVersion(1, 0, 0)
        v2 = SchemaVersion(1, 1, 0)
        assert v1 < v2

        # Test MigrationInfo
        info = MigrationInfo(
            source_version=v1,
            target_version=v2,
            description="Add new field",
        )
        assert info.source_version == v1

        return {"version_comparison": True, "migration_info_created": True}

    def test_migration_registry(self) -> dict:
        """Test migration registry."""
        from truthound.stores.migration.registry import MigrationRegistry
        from truthound.stores.migration.base import SchemaVersion

        registry = MigrationRegistry()

        # Register a migration
        v1 = SchemaVersion(1, 0, 0)
        v2 = SchemaVersion(1, 1, 0)
        v3 = SchemaVersion(2, 0, 0)

        @registry.register(v1, v2)
        def migrate_1_to_1_1(data):
            data["new_field"] = "default"
            return data

        @registry.register(v2, v3)
        def migrate_1_1_to_2(data):
            data["version"] = 2
            return data

        # Find migration path
        path = registry.find_path(v1, v3)
        assert len(path) == 2

        return {"migrations_registered": 2, "path_found": True}

    # ==========================================================================
    # 13. STREAMING STORAGE
    # ==========================================================================

    def test_streaming_config(self) -> dict:
        """Test streaming configuration."""
        from truthound.stores.streaming.base import (
            StreamingConfig,
            StreamingFormat,
            StreamSession,
            StreamStatus,
        )

        # Test formats
        assert StreamingFormat.JSONL is not None
        assert StreamingFormat.NDJSON is not None
        assert StreamingFormat.CSV is not None

        # Test status
        assert StreamStatus.PENDING is not None
        assert StreamStatus.ACTIVE is not None
        assert StreamStatus.COMPLETED is not None

        # Test config
        config = StreamingConfig(
            format=StreamingFormat.JSONL,
            chunk_size=1000,
            compression=None,
        )
        assert config.format == StreamingFormat.JSONL

        return {"formats": [f.value for f in StreamingFormat]}

    def test_streaming_session(self) -> dict:
        """Test streaming session management."""
        from truthound.stores.streaming.base import (
            StreamSession,
            StreamStatus,
            StreamingFormat,
        )

        session = StreamSession(
            session_id="stream-001",
            data_asset="large_data.csv",
            format=StreamingFormat.JSONL,
            status=StreamStatus.PENDING,
            created_at=datetime.now(UTC),
        )

        assert session.session_id == "stream-001"
        assert session.status == StreamStatus.PENDING

        return {"session_created": True}

    # ==========================================================================
    # 14. OBSERVABILITY
    # ==========================================================================

    def test_observability_audit_logging(self) -> dict:
        """Test audit logging for storage operations."""
        from truthound.stores.observability.audit import (
            AuditLogger,
            AuditEvent,
            AuditEventType,
            AuditLogLevel,
            AuditConfig,
            InMemoryAuditBackend,
        )

        backend = InMemoryAuditBackend()
        config = AuditConfig(level=AuditLogLevel.STANDARD)
        logger = AuditLogger(backend=backend, config=config)

        # Log operations
        logger.log(AuditEvent(
            event_type=AuditEventType.WRITE,
            resource_type="validation_result",
            resource_id="run-001",
            user_id="user_001",
        ))

        logger.log(AuditEvent(
            event_type=AuditEventType.READ,
            resource_type="validation_result",
            resource_id="run-001",
            user_id="user_001",
        ))

        # Query audit events
        events = backend.query(limit=10)

        assert len(events) >= 2

        return {"events_logged": len(events)}

    def test_observability_metrics(self) -> dict:
        """Test metrics collection for storage operations."""
        from truthound.stores.observability.metrics import (
            StoreMetrics,
            MetricsConfig,
        )

        config = MetricsConfig(enabled=True)
        metrics = StoreMetrics(store_name="test_store", config=config)

        # Record operations
        metrics.record_operation("save", duration_ms=15.5, success=True)
        metrics.record_operation("save", duration_ms=12.3, success=True)
        metrics.record_operation("get", duration_ms=5.2, success=True)
        metrics.record_operation("get", duration_ms=150.0, success=False)

        # Get statistics
        stats = metrics.get_stats()

        return {"operations_tracked": len(stats), "metrics_working": True}

    def test_observability_tracing(self) -> dict:
        """Test distributed tracing support."""
        from truthound.stores.observability.tracing import (
            TracingConfig,
            InMemoryTracer,
            SpanKind,
        )

        config = TracingConfig(
            service_name="truthound-storage",
            enabled=True,
        )

        tracer = InMemoryTracer(config=config)

        # Create spans
        with tracer.start_span("storage.save", kind=SpanKind.INTERNAL) as span:
            span.set_attribute("run_id", "run_001")
            span.set_attribute("data_asset", "data.csv")

            # Nested span
            with tracer.start_span("storage.serialize", kind=SpanKind.INTERNAL) as child_span:
                child_span.set_attribute("format", "json")

        # Get recorded spans
        spans = tracer.get_spans()

        return {"spans_created": len(spans)}

    # ==========================================================================
    # RUN ALL TESTS
    # ==========================================================================

    def run_all(self):
        """Run all test categories."""
        self.setup()

        try:
            # 1. Storage Backends
            self.run_category("Storage Backends", [
                ("Filesystem Backend", self.test_filesystem_backend),
                ("Memory Backend", self.test_memory_backend),
            ])

            # 2. Versioning
            self.run_category("Versioning", [
                ("Versioning Modes", self.test_versioning_modes),
                ("Versioning Config", self.test_versioning_config),
            ])

            # 3. Retention
            self.run_category("Retention Policies", [
                ("Retention Config", self.test_retention_config),
                ("Retention Policies", self.test_retention_policies),
            ])

            # 4. Caching
            self.run_category("Caching Layer", [
                ("In-Memory Cache", self.test_caching_inmemory),
                ("LRU Cache", self.test_caching_lru),
                ("LFU Cache", self.test_caching_lfu),
                ("TTL Cache", self.test_caching_ttl),
            ])

            # 5. Batching
            self.run_category("Batching Operations", [
                ("Batch Buffer", self.test_batching_buffer),
                ("Batch Writer", self.test_batching_writer),
            ])

            # 6. Encryption
            self.run_category("Encryption", [
                ("AES-GCM", self.test_encryption_aes_gcm),
                ("Fernet", self.test_encryption_fernet),
                ("Wrong Key Rejection", self.test_encryption_wrong_key),
            ])

            # 7. Compression
            self.run_category("Compression", [
                ("Gzip Compression", self.test_compression_gzip),
                ("Zstd Compression", self.test_compression_zstd),
                ("Adaptive Compression", self.test_compression_adaptive),
            ])

            # 8. Concurrency
            self.run_category("Concurrency Control", [
                ("Lock Strategy", self.test_concurrency_lock_strategy),
                ("Atomic Operations", self.test_concurrency_atomic_operations),
            ])

            # 9. Backpressure
            self.run_category("Backpressure Strategies", [
                ("Memory-Based Backpressure", self.test_backpressure_memory_based),
                ("Token Bucket", self.test_backpressure_token_bucket),
                ("Circuit Breaker", self.test_backpressure_circuit_breaker),
            ])

            # 10. Tiering
            self.run_category("Storage Tiering", [
                ("Tiering Types", self.test_tiering_types),
                ("Tiering Policies", self.test_tiering_policies),
            ])

            # 11. Replication
            self.run_category("Replication", [
                ("Replication Modes", self.test_replication_modes),
            ])

            # 12. Migration
            self.run_category("Schema Migration", [
                ("Migration Types", self.test_migration_types),
                ("Migration Registry", self.test_migration_registry),
            ])

            # 13. Streaming
            self.run_category("Streaming Storage", [
                ("Streaming Config", self.test_streaming_config),
                ("Streaming Session", self.test_streaming_session),
            ])

            # 14. Observability
            self.run_category("Observability", [
                ("Audit Logging", self.test_observability_audit_logging),
                ("Metrics Collection", self.test_observability_metrics),
                ("Distributed Tracing", self.test_observability_tracing),
            ])

        finally:
            self.teardown()

        # Print summary
        return self.print_summary()

    def print_summary(self):
        """Print test summary."""
        print("\n" + "=" * 60)
        print("📊 TEST SUMMARY")
        print("=" * 60)

        total_passed = 0
        total_failed = 0

        for category in self.categories:
            status = "✅" if category.failed == 0 else "❌"
            print(f"\n{status} {category.name}: {category.passed}/{category.total} passed")

            total_passed += category.passed
            total_failed += category.failed

            # Print failed tests
            for result in category.results:
                if not result.passed:
                    print(f"   ❌ {result.name}: {result.error}")

        print("\n" + "-" * 60)
        total = total_passed + total_failed
        success_rate = (total_passed / total * 100) if total > 0 else 0

        print(f"Total: {total_passed}/{total} tests passed ({success_rate:.1f}%)")

        if total_failed == 0:
            print("\n🎉 All tests passed!")
        else:
            print(f"\n⚠️  {total_failed} test(s) failed")

        return total_failed == 0


def main():
    """Run the functional test suite."""
    print("=" * 60)
    print("🧪 Truthound Storage Functional Test Suite")
    print("=" * 60)

    tests = StorageFunctionalTests()
    success = tests.run_all()

    sys.exit(0 if success else 1)


if __name__ == "__main__":
    main()
