"""
Integration tests for BGX library.

These tests verify that all components work together correctly.
"""

import pytest
import time
import threading
from concurrent.futures import ThreadPoolExecutor

import bgx
from bgx.task import TaskState, Priority, RetryPolicy


class TestBasicIntegration:
    """Test basic integration scenarios."""
    
    def test_simple_fire_and_forget(self):
        """Test simple fire-and-forget execution."""
        results = []
        
        def append_value(x):
            results.append(x)
            return x * 2
        
        engine = bgx.Engine()
        
        # Fire multiple tasks
        for i in range(5):
            engine.fire(append_value, i)
        
        # Wait for completion
        time.sleep(0.5)
        
        # All tasks should have executed
        assert len(results) == 5
        assert set(results) == {0, 1, 2, 3, 4}
        
        engine.shutdown()
    
    def test_tracked_task_execution(self):
        """Test tracked task execution with results."""
        def compute_square(x):
            time.sleep(0.1)  # Simulate work
            return x * x
        
        engine = bgx.Engine()
        
        # Submit tasks
        tasks = []
        for i in range(3):
            task = engine.submit(compute_square, i)
            tasks.append(task)
        
        # Wait for all results
        results = []
        for task in tasks:
            result = task.wait(timeout=2.0)
            results.append(result)
        
        assert results == [0, 1, 4]
        
        engine.shutdown()
    
    def test_priority_execution(self):
        """Test task priority affects execution order."""
        execution_order = []
        
        def log_task(name):
            execution_order.append(name)
            return name
        
        engine = bgx.Engine()
        
        # Submit tasks with different priorities
        low_task = engine.submit(log_task, "low", priority=bgx.Priority.LOW)
        high_task = engine.submit(log_task, "high", priority=bgx.Priority.HIGH)
        normal_task = engine.submit(log_task, "normal", priority=bgx.Priority.NORMAL)
        
        # Wait for completion
        low_task.wait(timeout=2.0)
        high_task.wait(timeout=2.0)
        normal_task.wait(timeout=2.0)
        
        # High priority should execute first
        assert execution_order[0] == "high"
        
        engine.shutdown()
    
    def test_task_tags_and_grouping(self):
        """Test task tagging and grouping."""
        tag_results = {}
        
        def tagged_work(tag, value):
            if tag not in tag_results:
                tag_results[tag] = []
            tag_results[tag].append(value)
            return value
        
        engine = bgx.Engine()
        
        # Submit tasks with different tags
        engine.submit(tagged_work, "email", 1, tag="email")
        engine.submit(tagged_work, "email", 2, tag="email")
        engine.submit(tagged_work, "db", 10, tag="database")
        engine.submit(tagged_work, "db", 20, tag="database")
        
        # Wait for completion
        time.sleep(0.5)
        
        # Check results by tag
        assert set(tag_results["email"]) == {1, 2}
        assert set(tag_results["database"]) == {10, 20}
        
        engine.shutdown()


class TestRetryIntegration:
    """Test retry functionality integration."""
    
    def test_successful_retry(self):
        """Test task that succeeds after retries."""
        attempt_count = 0
        
        def flaky_operation():
            nonlocal attempt_count
            attempt_count += 1
            if attempt_count < 3:
                raise ConnectionError("Temporary network issue")
            return "success"
        
        engine = bgx.Engine()
        
        task = engine.submit(
            flaky_operation,
            retry=bgx.RetryPolicy(
                max_attempts=5,
                strategy="exponential",
                base_delay=0.01
            )
        )
        
        result = task.wait(timeout=5.0)
        assert result == "success"
        assert attempt_count == 3
        
        engine.shutdown()
    
    def test_retry_exhaustion(self):
        """Test task that exhausts all retries."""
        attempt_count = 0
        
        def always_failing():
            nonlocal attempt_count
            attempt_count += 1
            raise RuntimeError("Permanent failure")
        
        engine = bgx.Engine()
        
        task = engine.submit(
            always_failing,
            retry=bgx.RetryPolicy(
                max_attempts=3,
                strategy="fixed",
                base_delay=0.01
            )
        )
        
        with pytest.raises(RuntimeError, match="Permanent failure"):
            task.wait(timeout=5.0)
        
        assert attempt_count == 3
        assert task.state == TaskState.DEAD
        
        engine.shutdown()
    
    def test_retry_with_different_strategies(self):
        """Test different retry backoff strategies."""
        attempt_times = []
        
        def time_tracking_operation():
            attempt_times.append(time.time())
            raise ValueError("Always fails")
        
        strategies = ["fixed", "linear", "exponential", "fibonacci"]
        
        for strategy in strategies:
            attempt_times.clear()
            
            engine = bgx.Engine()
            
            task = engine.submit(
                time_tracking_operation,
                retry=bgx.RetryPolicy(
                    max_attempts=3,
                    strategy=strategy,
                    base_delay=0.05,
                    jitter=False
                )
            )
            
            try:
                task.wait(timeout=5.0)
            except ValueError:
                pass  # Expected
            
            engine.shutdown()
            
            # Should have 3 attempts
            assert len(attempt_times) == 3
            
            # Check that delays increase for non-fixed strategies
            if strategy == "fixed":
                # All delays should be approximately the same
                delays = [attempt_times[i+1] - attempt_times[i] for i in range(2)]
                for delay in delays:
                    assert 0.04 <= delay <= 0.06  # Allow some tolerance


class TestVaultIntegration:
    """Test vault functionality integration."""
    
    def test_result_storage_and_retrieval(self):
        """Test storing and retrieving results through vault."""
        engine = bgx.Engine()
        
        def compute_value(x):
            return x ** 3
        
        # Submit task
        task = engine.submit(compute_value, 5)
        
        # Wait for completion
        result = task.wait(timeout=2.0)
        assert result == 125
        
        # Retrieve through engine
        stored_result = engine.get_result(task.id)
        assert stored_result == 125
        
        engine.shutdown()
    
    def test_vault_ttl_expiration(self):
        """Test vault TTL expiration."""
        engine = bgx.Engine(vault_ttl=0.1)  # 100ms TTL
        
        def quick_task():
            return "quick"
        
        # Submit task
        task = engine.submit(quick_task)
        
        # Wait for completion
        result = task.wait(timeout=2.0)
        assert result == "quick"
        
        # Should still be available immediately
        stored_result = engine.get_result(task.id)
        assert stored_result == "quick"
        
        # Wait for expiration
        time.sleep(0.2)
        
        # Should be expired
        with pytest.raises(KeyError):
            engine.get_result(task.id)
        
        engine.shutdown()


class TestConcurrencyIntegration:
    """Test concurrency and threading integration."""
    
    def test_concurrent_task_submission(self):
        """Test submitting tasks from multiple threads."""
        engine = bgx.Engine()
        results = []
        
        def worker_task(thread_id, value):
            results.append((thread_id, value))
            return thread_id * value
        
        # Submit tasks from multiple threads
        def submit_from_thread(thread_id):
            for i in range(5):
                engine.submit(worker_task, thread_id, i)
        
        threads = []
        for thread_id in range(3):
            thread = threading.Thread(target=submit_from_thread, args=(thread_id,))
            threads.append(thread)
            thread.start()
        
        # Wait for all threads to finish
        for thread in threads:
            thread.join()
        
        # Wait for tasks to complete
        time.sleep(1.0)
        
        # Should have 15 results total
        assert len(results) == 15
        
        # Check that all thread_ids are represented
        thread_ids = {result[0] for result in results}
        assert thread_ids == {0, 1, 2}
        
        engine.shutdown()
    
    def test_high_concurrency_load(self):
        """Test engine under high concurrency load."""
        engine = bgx.Engine(workers=10)
        completed_count = 0
        
        def cpu_intensive_task(n):
            # Simple CPU-bound work
            result = 0
            for i in range(n):
                result += i * i
            return result
        
        # Submit many tasks
        tasks = []
        for i in range(50):
            task = engine.submit(cpu_intensive_task, 1000)
            tasks.append(task)
        
        # Wait for all to complete
        for task in tasks:
            result = task.wait(timeout=10.0)
            assert result > 0
            completed_count += 1
        
        assert completed_count == 50
        
        # Check engine stats
        stats = engine.stats()
        assert stats["success"] >= 50
        
        engine.shutdown()


class TestHooksIntegration:
    """Test hook functionality integration."""
    
    def test_success_hooks(self):
        """Test success hook execution."""
        engine = bgx.Engine()
        hook_results = []
        
        def success_hook(result):
            hook_results.append(f"success: {result}")
        
        def simple_task(x):
            return x * 2
        
        task = engine.submit(
            simple_task, 7,
            on_success=success_hook
        )
        
        result = task.wait(timeout=2.0)
        assert result == 14
        
        # Hook should have been called
        assert len(hook_results) == 1
        assert "success: 14" in hook_results[0]
        
        engine.shutdown()
    
    def test_failure_hooks(self):
        """Test failure hook execution."""
        engine = bgx.Engine()
        hook_results = []
        
        def failure_hook(error):
            hook_results.append(f"failure: {error}")
        
        def failing_task():
            raise ValueError("Task failed")
        
        task = engine.submit(
            failing_task,
            on_failure=failure_hook
        )
        
        with pytest.raises(ValueError, match="Task failed"):
            task.wait(timeout=2.0)
        
        # Hook should have been called
        assert len(hook_results) == 1
        assert "failure: Task failed" in hook_results[0]
        
        engine.shutdown()
    
    def test_retry_hooks(self):
        """Test retry hook execution."""
        engine = bgx.Engine()
        hook_results = []
        attempt_count = 0
        
        def retry_hook(attempt, delay):
            hook_results.append(f"retry {attempt} after {delay:.3f}s")
        
        def flaky_task():
            nonlocal attempt_count
            attempt_count += 1
            if attempt_count < 3:
                raise ConnectionError("Temporary failure")
            return "success"
        
        task = engine.submit(
            flaky_task,
            retry=bgx.RetryPolicy(
                max_attempts=5,
                strategy="fixed",
                base_delay=0.01
            ),
            on_retry=retry_hook
        )
        
        result = task.wait(timeout=5.0)
        assert result == "success"
        
        # Should have 2 retry hooks (attempts 1 and 2)
        assert len(hook_results) == 2
        
        engine.shutdown()


class TestEngineLifecycle:
    """Test engine lifecycle management."""
    
    def test_graceful_shutdown(self):
        """Test graceful shutdown with task completion."""
        engine = bgx.Engine()
        completed_tasks = []
        
        def slow_task(task_id):
            time.sleep(0.2)
            completed_tasks.append(task_id)
            return task_id
        
        # Submit tasks
        tasks = []
        for i in range(3):
            task = engine.submit(slow_task, i)
            tasks.append(task)
        
        # Shutdown with wait
        start_time = time.time()
        engine.shutdown(wait=True)
        elapsed = time.time() - start_time
        
        # Should wait for tasks to complete
        assert elapsed >= 0.5  # Allow time for all tasks
        assert len(completed_tasks) == 3
    
    def test_immediate_shutdown(self):
        """Test immediate shutdown without waiting."""
        engine = bgx.Engine()
        
        def slow_task(task_id):
            time.sleep(1.0)
            return task_id
        
        # Submit tasks
        for i in range(3):
            engine.submit(slow_task, i)
        
        # Shutdown without waiting
        start_time = time.time()
        engine.shutdown(wait=False)
        elapsed = time.time() - start_time
        
        # Should return quickly
        assert elapsed < 0.5
