"""
Tests for the Engine module.
"""

import pytest
import time
from unittest.mock import Mock, patch

from bgx.engine import BackgroundEngine, create_engine, io_engine, cpu_engine
from bgx.task import Task, TaskState, Priority, RetryPolicy


class TestBackgroundEngine:
    """Test BackgroundEngine functionality."""
    
    def test_engine_creation(self):
        """Test engine creation with default parameters."""
        engine = BackgroundEngine()
        assert engine is not None
        assert not engine._shutdown
        
        # Check components are created
        assert engine.executor is not None
        assert engine.vault is not None
        assert engine.retry_manager is not None
    
    def test_engine_creation_custom(self):
        """Test engine creation with custom parameters."""
        engine = BackgroundEngine(
            workers=5,
            process_workers=2,
            mode="thread",
            vault_ttl=60.0,
            vault_max_size=500
        )
        
        assert engine is not None
        assert not engine._shutdown
    
    def test_fire_task(self):
        """Test fire-and-forget task submission."""
        engine = BackgroundEngine()
        
        result_container = []
        
        def sample_func(x):
            result_container.append(x)
            return x * 2
        
        # Fire task
        engine.fire(sample_func, 5)
        
        # Wait for completion
        time.sleep(0.1)
        
        # Task should have executed
        assert 5 in result_container
    
    def test_submit_task(self):
        """Test tracked task submission."""
        engine = BackgroundEngine()
        
        def sample_func(x):
            return x * 2
        
        # Submit task
        task = engine.submit(sample_func, 5)
        
        assert isinstance(task, Task)
        assert task.func == sample_func
        assert task.args == (5,)
        assert task.state in {TaskState.PENDING, TaskState.QUEUED, TaskState.RUNNING}
        
        # Wait for completion
        result = task.wait(timeout=2.0)
        assert result == 10
        assert task.state == TaskState.SUCCESS
    
    def test_submit_with_options(self):
        """Test task submission with all options."""
        engine = BackgroundEngine()
        
        success_called = []
        failure_called = []
        
        def success_hook(result):
            success_called.append(result)
        
        def failure_hook(error):
            failure_called.append(error)
        
        def sample_func(x):
            return x * 2
        
        # Submit task with options
        task = engine.submit(
            sample_func, 5,
            priority=Priority.HIGH,
            tag="test",
            timeout=30.0,
            retry=RetryPolicy(max_attempts=3),
            on_success=success_hook,
            on_failure=failure_hook
        )
        
        assert task.priority == Priority.HIGH
        assert task.tag == "test"
        assert task.timeout == 30.0
        assert task.retry_policy is not None
        assert task.on_success == success_hook
        assert task.on_failure == failure_hook
        
        # Wait for completion
        result = task.wait(timeout=2.0)
        assert result == 10
        
        # Success hook should be called
        assert len(success_called) == 1
        assert success_called[0] == 10
        assert len(failure_called) == 0
    
    def test_get_result(self):
        """Test getting task result."""
        engine = BackgroundEngine()
        
        def sample_func(x):
            return x * 2
        
        # Submit task
        task = engine.submit(sample_func, 7)
        
        # Get result by ID
        result = engine.get_result(task.id, timeout=2.0)
        assert result == 14
    
    def test_get_result_error(self):
        """Test getting task result that raised an error."""
        engine = BackgroundEngine()
        
        def failing_func():
            raise ValueError("test error")
        
        # Submit task
        task = engine.submit(failing_func)
        
        # Get result should raise the same error
        with pytest.raises(ValueError, match="test error"):
            engine.get_result(task.id, timeout=2.0)
    
    def test_get_result_not_found(self):
        """Test getting result for non-existent task."""
        engine = BackgroundEngine()
        
        with pytest.raises(KeyError):
            engine.get_result("nonexistent", timeout=0.1)
    
    def test_get_task(self):
        """Test getting task by ID."""
        engine = BackgroundEngine()
        
        def sample_func(x):
            return x * 2
        
        # Submit task
        task = engine.submit(sample_func, 5)
        
        # Get task
        retrieved_task = engine.get_task(task.id)
        assert retrieved_task is task
        assert retrieved_task.id == task.id
        
        # Get non-existent task
        non_existent = engine.get_task("nonexistent")
        assert non_existent is None
    
    def test_list_tasks(self):
        """Test listing tasks."""
        engine = BackgroundEngine()
        
        def sample_func(x):
            return x * 2
        
        # Submit tasks
        task1 = engine.submit(sample_func, 1, tag="group1")
        task2 = engine.submit(sample_func, 2, tag="group2")
        task3 = engine.submit(sample_func, 3, tag="group1")
        
        # List all tasks
        all_tasks = engine.list_tasks()
        assert len(all_tasks) >= 3
        
        # List by state
        pending_tasks = engine.list_tasks(state=TaskState.PENDING)
        assert len(pending_tasks) >= 0  # May have already started
        
        # List by tag
        group1_tasks = engine.list_tasks(tag="group1")
        assert len(group1_tasks) == 2
        
        group2_tasks = engine.list_tasks(tag="group2")
        assert len(group2_tasks) == 1
    
    def test_cancel_task(self):
        """Test canceling a task."""
        engine = BackgroundEngine()
        
        def slow_func():
            time.sleep(1.0)
            return "done"
        
        # Submit task
        task = engine.submit(slow_func)
        
        # Cancel immediately
        cancelled = engine.cancel(task.id)
        assert cancelled is True
        assert task.state == TaskState.CANCELLED
        
        # Try to cancel again
        cancelled = engine.cancel(task.id)
        assert cancelled is False
        
        # Try to cancel non-existent task
        cancelled = engine.cancel("nonexistent")
        assert cancelled is False
    
    def test_stats(self):
        """Test getting engine statistics."""
        engine = BackgroundEngine()
        
        def sample_func(x):
            return x * 2
        
        # Submit some tasks
        task1 = engine.submit(sample_func, 1)
        task2 = engine.submit(sample_func, 2)
        
        # Get stats
        stats = engine.stats()
        
        assert "total_tasks" in stats
        assert "active" in stats
        assert "queued" in stats
        assert "success" in stats
        assert "failed" in stats
        assert "dead" in stats
        assert "retrying" in stats
        assert "vault_stats" in stats
        assert "tag_info" in stats
        
        assert stats["total_tasks"] >= 2
    
    def test_set_tag_concurrency(self):
        """Test setting tag concurrency limits."""
        engine = BackgroundEngine()
        
        # Set concurrency for tag
        engine.set_tag_concurrency("test_tag", 3)
        
        # Check tag info
        stats = engine.stats()
        tag_info = stats["tag_info"]
        
        # Tag should be in info (may need to submit a task first)
        # This is implementation-dependent, so we just ensure no error
        assert isinstance(tag_info, dict)
    
    def test_context_manager(self):
        """Test engine as context manager."""
        def sample_func(x):
            return x * 2
        
        with BackgroundEngine() as engine:
            assert not engine._shutdown
            
            # Submit task
            task = engine.submit(sample_func, 5)
            result = task.wait(timeout=2.0)
            assert result == 10
        
        # Engine should be shutdown after context
        assert engine._shutdown
    
    def test_shutdown(self):
        """Test engine shutdown."""
        engine = BackgroundEngine()
        
        def sample_func(x):
            return x * 2
        
        # Submit task
        task = engine.submit(sample_func, 5)
        
        # Shutdown without waiting
        engine.shutdown(wait=False)
        assert engine._shutdown
        
        # Should not be able to submit after shutdown
        with pytest.raises(RuntimeError, match="Engine is shutdown"):
            engine.submit(sample_func, 10)
    
    def test_shutdown_wait(self):
        """Test engine shutdown with waiting."""
        engine = BackgroundEngine()
        
        def sample_func(x):
            time.sleep(0.1)
            return x * 2
        
        # Submit tasks
        task1 = engine.submit(sample_func, 5)
        task2 = engine.submit(sample_func, 10)
        
        # Shutdown and wait
        start_time = time.time()
        engine.shutdown(wait=True)
        elapsed = time.time() - start_time
        
        # Should wait for tasks to complete
        assert elapsed >= 0.1
        assert engine._shutdown
    
    def test_retry_functionality(self):
        """Test task retry functionality."""
        engine = BackgroundEngine()
        
        attempt_count = 0
        
        def flaky_func():
            nonlocal attempt_count
            attempt_count += 1
            if attempt_count < 3:
                raise ValueError("Temporary failure")
            return "success"
        
        # Submit task with retry policy
        task = engine.submit(
            flaky_func,
            retry=RetryPolicy(
                max_attempts=3,
                strategy="fixed",
                base_delay=0.01
            )
        )
        
        # Wait for completion
        result = task.wait(timeout=5.0)
        assert result == "success"
        assert task.state == TaskState.SUCCESS
        assert attempt_count == 3
    
    def test_retry_exhausted(self):
        """Test retry exhaustion."""
        engine = BackgroundEngine()
        
        def always_failing_func():
            raise ValueError("Permanent failure")
        
        # Submit task with retry policy
        task = engine.submit(
            always_failing_func,
            retry=RetryPolicy(
                max_attempts=2,
                strategy="fixed",
                base_delay=0.01
            )
        )
        
        # Wait for completion
        with pytest.raises(ValueError, match="Permanent failure"):
            task.wait(timeout=5.0)
        
        assert task.state == TaskState.DEAD
        assert task.attempt == 2


class TestEngineUtilities:
    """Test engine utility functions."""
    
    def test_create_engine(self):
        """Test create_engine utility function."""
        engine = create_engine(
            workers=5,
            process_workers=2,
            mode="thread",
            vault_ttl=60.0
        )
        
        assert isinstance(engine, BackgroundEngine)
    
    def test_io_engine(self):
        """Test io_engine utility function."""
        engine = io_engine(workers=10)
        assert isinstance(engine, BackgroundEngine)
    
    def test_cpu_engine(self):
        """Test cpu_engine utility function."""
        engine = cpu_engine(process_workers=4)
        assert isinstance(engine, BackgroundEngine)
