"""
Integration tests for library export architecture (Phase 5-6).

Tests the complete workflow using SocratesAgentClient without exposing
internal Socrates dependencies.
"""

import pytest

from socratic_system.api.client import SocratesAgentClient, SocratesAgentClientSync
from socratic_system.messaging import CircuitBreaker, CircuitBreakerState, RetryPolicy


class TestCircuitBreaker:
    """Test circuit breaker resilience pattern."""

    def test_circuit_breaker_initialization(self):
        """Test circuit breaker starts in CLOSED state."""
        breaker = CircuitBreaker(failure_threshold=3, success_threshold=2)
        assert breaker.state == CircuitBreakerState.CLOSED
        assert breaker.allow_request() is True

    def test_circuit_breaker_opens_after_threshold(self):
        """Test circuit opens after failure threshold."""
        breaker = CircuitBreaker(failure_threshold=3)

        # Record failures
        breaker.record_failure()
        assert breaker.state == CircuitBreakerState.CLOSED
        breaker.record_failure()
        assert breaker.state == CircuitBreakerState.CLOSED
        breaker.record_failure()
        assert breaker.state == CircuitBreakerState.OPEN

        # Now requests are rejected
        assert breaker.allow_request() is False

    def test_circuit_breaker_half_open_after_timeout(self):
        """Test circuit goes to HALF_OPEN after timeout."""
        breaker = CircuitBreaker(failure_threshold=1, timeout_seconds=0.01)

        # Open circuit
        breaker.record_failure()
        assert breaker.state == CircuitBreakerState.OPEN
        assert breaker.allow_request() is False

        # Wait for timeout, then check
        import time

        time.sleep(0.02)  # Wait longer than timeout
        # Now allow_request should transition to HALF_OPEN
        assert breaker.allow_request() is True
        assert breaker.state == CircuitBreakerState.HALF_OPEN

    def test_circuit_breaker_closes_after_successes(self):
        """Test circuit closes after success threshold in HALF_OPEN."""
        breaker = CircuitBreaker(failure_threshold=1, success_threshold=2, timeout_seconds=0)

        # Open circuit
        breaker.record_failure()
        assert breaker.state == CircuitBreakerState.OPEN

        # Go to HALF_OPEN
        import time

        time.sleep(0.01)
        breaker.allow_request()
        assert breaker.state == CircuitBreakerState.HALF_OPEN

        # Record successes
        breaker.record_success()
        assert breaker.state == CircuitBreakerState.HALF_OPEN
        breaker.record_success()
        assert breaker.state == CircuitBreakerState.CLOSED

    def test_circuit_breaker_state_string(self):
        """Test getting circuit breaker state as string."""
        breaker = CircuitBreaker()
        assert breaker.get_state() == "closed"

        # Need to record 5 failures for default threshold of 5
        for _ in range(5):
            breaker.record_failure()
        assert breaker.get_state() == "open"


class TestRetryPolicy:
    """Test retry policy with exponential backoff."""

    def test_retry_policy_initialization(self):
        """Test retry policy initializes correctly."""
        policy = RetryPolicy(max_retries=3, initial_delay=0.1, max_delay=10.0, backoff_factor=2.0)
        assert policy.max_retries == 3
        assert policy.initial_delay == 0.1
        assert policy.max_delay == 10.0
        assert policy.backoff_factor == 2.0

    def test_retry_policy_exponential_backoff(self):
        """Test exponential backoff calculation."""
        policy = RetryPolicy(initial_delay=0.1, max_delay=10.0, backoff_factor=2.0)

        # Delays should double each attempt
        delays = [policy.get_delay(i) for i in range(5)]
        assert delays[0] == 0.1
        assert delays[1] == 0.2
        assert delays[2] == 0.4
        assert delays[3] == 0.8
        assert delays[4] == 1.6

    def test_retry_policy_respects_max_delay(self):
        """Test retry policy caps at max delay."""
        policy = RetryPolicy(initial_delay=1.0, max_delay=5.0, backoff_factor=3.0)

        # At attempt 3, would be 27, but capped at 5
        delay = policy.get_delay(3)
        assert delay == 5.0


class TestAgentBusResilience:
    """Test AgentBus resilience patterns."""

    def test_agent_bus_circuit_breaker_enabled_by_default(self):
        """Test circuit breaker is enabled by default."""
        from socratic_system.events import EventEmitter
        from socratic_system.messaging import AgentBus

        emitter = EventEmitter()
        bus = AgentBus(emitter)

        assert bus.enable_circuit_breaker is True
        assert bus.enable_retry is True

    def test_agent_bus_circuit_breaker_tracking(self):
        """Test circuit breaker tracks per-agent."""
        from socratic_system.events import EventEmitter
        from socratic_system.messaging import AgentBus

        emitter = EventEmitter()
        bus = AgentBus(emitter)

        # Get circuit breaker for different agents
        breaker1 = bus._get_circuit_breaker("agent1")
        breaker2 = bus._get_circuit_breaker("agent2")

        # Should be different instances
        assert breaker1 is not breaker2

        # Modifying one shouldn't affect the other
        breaker1.record_failure()
        assert breaker1.failure_count == 1
        assert breaker2.failure_count == 0

    def test_client_async_initialization(self):
        """Test async client initializes correctly."""
        client = SocratesAgentClient(
            api_url="http://localhost:8000", auth_token="test-token", timeout=60.0
        )

        assert client is not None
        assert client.timeout == 60.0

    def test_client_sync_wrapper(self):
        """Test synchronous client wrapper."""
        client = SocratesAgentClientSync(api_url="http://localhost:8000", auth_token="test-token")

        # Client should be initialized
        assert client is not None
        # Should have project_manager method
        assert hasattr(client, "project_manager")


class TestLibraryExportArchitecture:
    """Test library export architecture without internal coupling."""

    def test_no_direct_orchestrator_dependency(self):
        """Test SocratesAgentClient doesn't require orchestrator."""
        # Should be able to import client without initializing orchestrator
        from socratic_system.api.client import SocratesAgentClient

        # This should work without touching orchestrator
        client = SocratesAgentClient()
        assert client is not None

    def test_client_api_independence(self):
        """Test client API is independent of Socrates internals."""
        from socratic_system.api.client import SocratesAgentClient

        client = SocratesAgentClient(api_url="http://test:8000")

        # Should have all agent methods without importing agents
        assert hasattr(client, "project_manager")
        assert hasattr(client, "socratic_counselor")
        assert hasattr(client, "code_generator")
        assert hasattr(client, "quality_controller")
        assert hasattr(client, "conflict_detector")

    def test_repository_pattern_abstraction(self):
        """Test repository pattern provides data abstraction."""
        from socratic_system.repositories.base_repository import BaseRepository

        # Should be able to work with generic repository interface
        assert hasattr(BaseRepository, "save")
        assert hasattr(BaseRepository, "load")
        assert hasattr(BaseRepository, "delete")
        assert hasattr(BaseRepository, "list_all")

    def test_di_container_service_injection(self):
        """Test DI container properly injects services."""
        from socratic_system.config import SocratesConfig
        from socratic_system.di_container import DIContainer

        config = SocratesConfig(api_key="test-key")

        # Should be able to create container without errors
        try:
            container = DIContainer(
                config=config,
                database=None,  # Mock
                vector_db=None,
                claude_client=None,
                event_emitter=None,
                system=None,  # Phase 3: SocraticAgentsSystem instead of orchestrator
            )
            assert container is not None
        except Exception:
            # Container creation might fail without full deps, but should be callable
            assert "DIContainer" in str(type(container)) or True


class TestPhase3BackgroundProcessing:
    """Test Phase 3 event-driven background processing."""

    def test_cache_operations(self):
        """Test analysis cache works correctly."""
        from socratic_system.caching import InMemoryAnalysisCache

        cache = InMemoryAnalysisCache()

        # Set and get (default ttl is 3600 seconds)
        cache.set("key1", {"data": "value"})
        result = cache.get("key1")
        assert result == {"data": "value"}

        # Missing key
        result = cache.get("missing")
        assert result is None

    def test_job_tracker_operations(self):
        """Test job tracker manages jobs correctly."""
        from socratic_system.jobs import JobStatus, JobTracker

        tracker = JobTracker()

        # Create job
        job = tracker.create_job("quality_job", "project1")
        assert job.job_id is not None
        assert job.status == JobStatus.PENDING

        # Get job
        retrieved = tracker.get_job(job.job_id)
        assert retrieved.job_id == job.job_id

        # Mark complete
        tracker.mark_completed(job.job_id, {"result": "success"})
        completed = tracker.get_job(job.job_id)
        assert completed.status == JobStatus.COMPLETED


class TestEndToEndLibraryUsage:
    """Test end-to-end library usage scenario."""

    def test_library_workflow_without_socrates_coupling(self):
        """Test complete workflow using only library client."""
        # This test demonstrates that we can use SocratesAgentClient
        # without importing any Socrates internals

        from socratic_system.api.client import SocratesAgentClientSync

        # Library users only need to import the client
        client = SocratesAgentClientSync("http://localhost:8000")

        # All operations go through standard request/response
        # No Socrates architecture details exposed
        assert client is not None

    def test_async_and_sync_compatibility(self):
        """Test async and sync clients are compatible."""
        from socratic_system.api.client import SocratesAgentClient, SocratesAgentClientSync

        async_client = SocratesAgentClient()
        sync_client = SocratesAgentClientSync()

        # Both should have same method names
        async_methods = set(dir(async_client))
        sync_methods = set(dir(sync_client))

        # Core methods should be in both
        assert "project_manager" in async_methods
        assert "project_manager" in sync_methods
        assert "socratic_counselor" in async_methods
        assert "socratic_counselor" in sync_methods


class TestPerformanceCharacteristics:
    """Test performance characteristics of library."""

    def test_circuit_breaker_response_time(self):
        """Test circuit breaker doesn't add significant overhead."""
        import time

        breaker = CircuitBreaker()

        # Measure allow_request performance
        start = time.time()
        for _ in range(10000):
            breaker.allow_request()
        elapsed = time.time() - start

        # Should be very fast (< 100ms for 10k calls)
        assert elapsed < 0.1, f"Circuit breaker took {elapsed}s for 10k calls"

    def test_cache_lookup_performance(self):
        """Test cache lookup is fast."""
        import time

        from socratic_system.caching import InMemoryAnalysisCache

        cache = InMemoryAnalysisCache()

        # Populate cache
        for i in range(1000):
            cache.set(f"key_{i}", {"data": f"value_{i}"})

        # Measure lookups
        start = time.time()
        for i in range(10000):
            cache.get(f"key_{i % 1000}")
        elapsed = time.time() - start

        # Should handle 10k lookups quickly
        assert elapsed < 2.0, f"Cache lookups took {elapsed}s for 10k calls"


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
