"""
Tests for the Task module.
"""

import pytest
import time
import uuid
from unittest.mock import Mock

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


class TestTaskState:
    """Test TaskState enum."""
    
    def test_task_state_values(self):
        """Test TaskState enum values."""
        assert TaskState.PENDING.value == "pending"
        assert TaskState.QUEUED.value == "queued"
        assert TaskState.RUNNING.value == "running"
        assert TaskState.SUCCESS.value == "success"
        assert TaskState.FAILED.value == "failed"
        assert TaskState.RETRYING.value == "retrying"
        assert TaskState.DEAD.value == "dead"
        assert TaskState.TIMEOUT.value == "timeout"
        assert TaskState.CANCELLED.value == "cancelled"


class TestPriority:
    """Test Priority enum."""
    
    def test_priority_values(self):
        """Test Priority enum values."""
        assert Priority.HIGH.value == 0
        assert Priority.NORMAL.value == 1
        assert Priority.LOW.value == 2


class TestRetryPolicy:
    """Test RetryPolicy dataclass."""
    
    def test_default_values(self):
        """Test RetryPolicy default values."""
        policy = RetryPolicy()
        assert policy.max_attempts == 3
        assert policy.strategy == "exponential"
        assert policy.base_delay == 1.0
        assert policy.max_delay == 60.0
        assert policy.jitter is True
        assert policy.retry_on == [Exception]
        assert policy.abort_on == []
    
    def test_custom_values(self):
        """Test RetryPolicy with custom values."""
        policy = RetryPolicy(
            max_attempts=5,
            strategy="linear",
            base_delay=2.0,
            max_delay=120.0,
            jitter=False,
            retry_on=[ValueError, TypeError],
            abort_on=[RuntimeError]
        )
        assert policy.max_attempts == 5
        assert policy.strategy == "linear"
        assert policy.base_delay == 2.0
        assert policy.max_delay == 120.0
        assert policy.jitter is False
        assert policy.retry_on == [ValueError, TypeError]
        assert policy.abort_on == [RuntimeError]


class TestTask:
    """Test Task dataclass."""
    
    def test_task_creation(self):
        """Test basic task creation."""
        def sample_func(x, y):
            return x + y
        
        task = Task(
            func=sample_func,
            args=(1, 2),
            kwargs={"z": 3},
            priority=Priority.HIGH,
            tag="test",
            timeout=30.0
        )
        
        assert task.func == sample_func
        assert task.args == (1, 2)
        assert task.kwargs == {"z": 3}
        assert task.state == TaskState.PENDING
        assert task.priority == Priority.HIGH
        assert task.tag == "test"
        assert task.timeout == 30.0
        assert task.attempt == 1
        assert task.result is None
        assert task.error is None
        assert isinstance(task.id, str)
        assert isinstance(task.created_at, float)
        assert task.started_at is None
        assert task.finished_at is None
    
    def test_task_validation(self):
        """Test task validation."""
        def sample_func():
            pass
        
        # Test invalid timeout
        with pytest.raises(ValueError, match="timeout must be positive"):
            Task(func=sample_func, timeout=-1.0)
        
        # Test invalid retry policy
        with pytest.raises(ValueError, match="max_attempts must be at least 1"):
            Task(func=sample_func, retry_policy=RetryPolicy(max_attempts=0))
        
        with pytest.raises(ValueError, match="base_delay must be positive"):
            Task(func=sample_func, retry_policy=RetryPolicy(base_delay=-1.0))
        
        with pytest.raises(ValueError, match="max_delay must be positive"):
            Task(func=sample_func, retry_policy=RetryPolicy(max_delay=-1.0))
    
    def test_state_transitions(self):
        """Test valid state transitions."""
        def sample_func():
            pass
        
        task = Task(func=sample_func)
        
        # Valid transitions
        task.transition_to(TaskState.QUEUED)
        assert task.state == TaskState.QUEUED
        
        task.transition_to(TaskState.RUNNING)
        assert task.state == TaskState.RUNNING
        assert task.started_at is not None
        
        task.transition_to(TaskState.SUCCESS)
        assert task.state == TaskState.SUCCESS
        assert task.finished_at is not None
        
        # Test timestamp updates
        original_created = task.created_at
        original_started = task.started_at
        original_finished = task.finished_at
        
        time.sleep(0.01)
        
        new_task = Task(func=sample_func)
        new_task.transition_to(TaskState.QUEUED)
        new_task.transition_to(TaskState.RUNNING)
        new_task.transition_to(TaskState.FAILED)
        
        assert new_task.created_at > original_created
        assert new_task.started_at > original_started
        assert new_task.finished_at > original_finished
    
    def test_invalid_state_transitions(self):
        """Test invalid state transitions."""
        def sample_func():
            pass
        
        task = Task(func=sample_func)
        
        # Invalid transitions
        with pytest.raises(ValueError, match="Invalid state transition"):
            task.transition_to(TaskState.SUCCESS)  # Can't go from PENDING to SUCCESS
        
        task.transition_to(TaskState.QUEUED)
        
        with pytest.raises(ValueError, match="Invalid state transition"):
            task.transition_to(TaskState.SUCCESS)  # Can't go from QUEUED to SUCCESS
        
        task.transition_to(TaskState.RUNNING)
        
        with pytest.raises(ValueError, match="Invalid state transition"):
            task.transition_to(TaskState.QUEUED)  # Can't go from RUNNING to QUEUED
    
    def test_can_retry(self):
        """Test retry eligibility."""
        def sample_func():
            pass
        
        # No retry policy
        task = Task(func=sample_func)
        task.error = ValueError("test")
        assert not task.can_retry()
        
        # With retry policy but no error
        task.retry_policy = RetryPolicy(max_attempts=3)
        task.error = None
        assert not task.can_retry()
        
        # With retry policy and error
        task.error = ValueError("test")
        assert task.can_retry()
        
        # Max attempts reached
        task.attempt = 3
        assert not task.can_retry()
        
        # Reset attempt and test abort_on
        task.attempt = 1
        task.retry_policy = RetryPolicy(max_attempts=3, abort_on=[ValueError])
        assert not task.can_retry()
        
        # Test retry_on filtering
        task.retry_policy = RetryPolicy(max_attempts=3, retry_on=[TypeError])
        task.error = TypeError("test")
        assert task.can_retry()
        task.error = ValueError("test")
        assert not task.can_retry()
    
    def test_compute_retry_delay(self):
        """Test retry delay computation."""
        def sample_func():
            pass
        
        task = Task(func=sample_func)
        
        # No retry policy
        assert task.compute_retry_delay() == 0.0
        
        # Fixed strategy
        task.retry_policy = RetryPolicy(strategy="fixed", base_delay=2.0, jitter=False)
        assert task.compute_retry_delay() == 2.0
        
        # Linear strategy - create new policy
        task.retry_policy = RetryPolicy(strategy="linear", base_delay=2.0, jitter=False)
        assert task.compute_retry_delay() == 2.0  # base_delay * attempt (1)
        
        task.attempt = 2
        assert task.compute_retry_delay() == 4.0  # base_delay * attempt (2)
        
        # Exponential strategy - create new policy
        task.attempt = 1
        task.retry_policy = RetryPolicy(strategy="exponential", base_delay=2.0, jitter=False)
        assert task.compute_retry_delay() == 2.0  # base_delay * 2^(1-1)
        
        task.attempt = 2
        assert task.compute_retry_delay() == 4.0  # base_delay * 2^(2-1)
        
        # Fibonacci strategy - create new policy
        task.attempt = 1
        task.retry_policy = RetryPolicy(strategy="fibonacci", base_delay=2.0, jitter=False)
        assert task.compute_retry_delay() == 2.0  # base_delay * fib(1)
        
        task.attempt = 2
        assert task.compute_retry_delay() == 2.0  # base_delay * fib(2)
        
        task.attempt = 3
        assert task.compute_retry_delay() == 4.0  # base_delay * fib(3)
        
        # Max delay cap
        task.retry_policy = RetryPolicy(strategy="fibonacci", base_delay=2.0, max_delay=3.0, jitter=False)
        task.attempt = 4
        delay = task.compute_retry_delay()
        assert delay <= 3.0
        
        # Test jitter
        task.retry_policy = RetryPolicy(strategy="fixed", base_delay=2.0, jitter=True)
        delay1 = task.compute_retry_delay()
        delay2 = task.compute_retry_delay()
        # With jitter, delays should be different (most of the time)
        # Note: This is probabilistic, so we just check it's within bounds
        assert 2.0 <= delay1 <= 2.5
        assert 2.0 <= delay2 <= 2.5
    
    def test_fibonacci(self):
        """Test Fibonacci calculation."""
        def sample_func():
            pass
        
        task = Task(func=sample_func)
        
        assert task._fibonacci(0) == 0
        assert task._fibonacci(1) == 1
        assert task._fibonacci(2) == 1
        assert task._fibonacci(3) == 2
        assert task._fibonacci(4) == 3
        assert task._fibonacci(5) == 5
        assert task._fibonacci(6) == 8
    
    def test_wait_success(self):
        """Test waiting for successful task."""
        def sample_func():
            return "success"
        
        task = Task(func=sample_func)
        task.result = "success"
        task.transition_to(TaskState.QUEUED)
        task.transition_to(TaskState.RUNNING)
        task.transition_to(TaskState.SUCCESS)
        
        result = task.wait(timeout=1.0)
        assert result == "success"
    
    def test_wait_failure(self):
        """Test waiting for failed task."""
        def sample_func():
            pass
        
        task = Task(func=sample_func)
        error = ValueError("test error")
        task.error = error
        task.transition_to(TaskState.QUEUED)
        task.transition_to(TaskState.RUNNING)
        task.transition_to(TaskState.FAILED)
        
        with pytest.raises(ValueError, match="test error"):
            task.wait(timeout=1.0)
    
    def test_wait_timeout(self):
        """Test waiting for timed out task."""
        def sample_func():
            pass
        
        task = Task(func=sample_func)
        task.transition_to(TaskState.QUEUED)
        task.transition_to(TaskState.RUNNING)
        task.transition_to(TaskState.TIMEOUT)
        
        with pytest.raises(TimeoutError, match="timed out"):
            task.wait(timeout=1.0)
    
    def test_wait_cancelled(self):
        """Test waiting for cancelled task."""
        def sample_func():
            pass
        
        task = Task(func=sample_func)
        task.transition_to(TaskState.CANCELLED)
        
        with pytest.raises(RuntimeError, match="was cancelled"):
            task.wait(timeout=1.0)
    
    def test_wait_actual_timeout(self):
        """Test wait method timeout."""
        def sample_func():
            pass
        
        task = Task(func=sample_func)
        # Keep task in PENDING state to trigger timeout
        
        with pytest.raises(TimeoutError, match="did not complete"):
            task.wait(timeout=0.01)
    
    def test_repr(self):
        """Test task string representation."""
        def sample_func():
            pass
        
        task = Task(func=sample_func)
        task.transition_to(TaskState.QUEUED)
        task.transition_to(TaskState.RUNNING)
        
        repr_str = repr(task)
        assert "Task(" in repr_str
        assert task.id[:8] in repr_str
        assert "running" in repr_str
        assert "sample_func" in repr_str
