"""
Tests for GateWay Worker Pool Module.
"""


import pytest
import pytest_asyncio

from verlex_server.gateway.backend.worker_pool import (
    LocalWorkerProvisioner,
    PoolConfig,
    PoolMetrics,
    Worker,
    WorkerPool,
    WorkerPoolManager,
    WorkerResources,
    WorkerStatus,
    WorkerType,
    get_worker_pool,
)


class TestWorkerResources:
    """Tests for WorkerResources dataclass."""

    def test_default_resources(self):
        """Test default resource values (0 = not yet configured by LLM)."""
        resources = WorkerResources()

        assert resources.cpu_cores == 0
        assert resources.memory_gb == 0.0
        assert resources.gpu_count == 0

    def test_custom_resources(self):
        """Test custom resource values."""
        resources = WorkerResources(
            cpu_cores=8,
            memory_gb=32.0,
            gpu_count=2,
            gpu_type="nvidia-a100",
        )

        assert resources.cpu_cores == 8
        assert resources.memory_gb == 32.0
        assert resources.gpu_count == 2
        assert resources.gpu_type == "nvidia-a100"

    def test_can_run(self):
        """Test can_run check."""
        resources = WorkerResources(cpu_cores=4, memory_gb=8.0)

        assert resources.can_run(cpu=2, memory_gb=4.0)
        assert not resources.can_run(cpu=10, memory_gb=4.0)
        assert not resources.can_run(cpu=2, memory_gb=16.0)

    def test_has_gpu(self):
        """Test GPU detection."""
        no_gpu = WorkerResources()
        assert not no_gpu.has_gpu

        with_gpu = WorkerResources(gpu_type="nvidia-a100", gpu_count=1)
        assert with_gpu.has_gpu

    def test_allocate_release(self):
        """Test resource allocation and release."""
        resources = WorkerResources(cpu_cores=4, memory_gb=8.0)

        # Allocate
        assert resources.allocate(cpu=2, memory_gb=4.0)
        assert resources.cpu_used == 2
        assert resources.memory_used_gb == 4.0

        # Release
        resources.release(cpu=2, memory_gb=4.0)
        assert resources.cpu_used == 0
        assert resources.memory_used_gb == 0

    def test_resources_to_dict(self):
        """Test resources serialization."""
        resources = WorkerResources(cpu_cores=4, memory_gb=16.0)

        data = resources.to_dict()

        assert data["cpu_cores"] == 4
        assert data["memory_gb"] == 16.0


class TestWorker:
    """Tests for Worker dataclass."""

    def test_worker_creation(self):
        """Test creating a worker."""
        worker = Worker(
            worker_id="worker_123",
            worker_type=WorkerType.LOCAL,
            resources=WorkerResources(cpu_cores=4),
        )

        assert worker.worker_id == "worker_123"
        assert worker.worker_type == WorkerType.LOCAL
        assert worker.status == WorkerStatus.IDLE
        assert worker.resources.cpu_cores == 4

    def test_worker_is_available(self):
        """Test worker availability check."""
        worker = Worker(
            worker_id="worker_1",
            worker_type=WorkerType.LOCAL,
        )

        assert worker.is_available

        worker.status = WorkerStatus.BUSY
        assert not worker.is_available

    def test_worker_is_healthy(self):
        """Test worker health check."""
        worker = Worker(
            worker_id="worker_1",
            worker_type=WorkerType.LOCAL,
        )

        assert worker.is_healthy

        worker.status = WorkerStatus.UNHEALTHY
        assert not worker.is_healthy

    def test_worker_heartbeat(self):
        """Test worker heartbeat."""
        worker = Worker(
            worker_id="worker_1",
            worker_type=WorkerType.LOCAL,
        )

        old_heartbeat = worker.last_heartbeat
        worker.heartbeat()

        assert worker.last_heartbeat >= old_heartbeat

    def test_worker_to_dict(self):
        """Test worker serialization."""
        worker = Worker(
            worker_id="worker_1",
            worker_type=WorkerType.LOCAL,
            status=WorkerStatus.IDLE,
        )

        data = worker.to_dict()

        assert data["worker_id"] == "worker_1"
        assert data["status"] == "idle"


class TestPoolConfig:
    """Tests for PoolConfig dataclass."""

    def test_default_config(self):
        """Test default pool configuration."""
        config = PoolConfig()

        assert config.min_workers == 1
        assert config.max_workers == 10
        assert config.scale_up_threshold == 0.8
        assert config.scale_down_threshold == 0.3

    def test_custom_config(self):
        """Test custom pool configuration."""
        config = PoolConfig(
            min_workers=2,
            max_workers=50,
            warm_pool_size=5,
        )

        assert config.min_workers == 2
        assert config.max_workers == 50
        assert config.warm_pool_size == 5


class TestPoolMetrics:
    """Tests for PoolMetrics dataclass."""

    def test_default_metrics(self):
        """Test default metrics."""
        metrics = PoolMetrics()

        assert metrics.total_workers == 0
        assert metrics.available_workers == 0
        assert metrics.busy_workers == 0

    def test_metrics_to_dict(self):
        """Test metrics serialization."""
        metrics = PoolMetrics(
            total_workers=10,
            available_workers=6,
            busy_workers=4,
        )

        data = metrics.to_dict()

        assert data["total_workers"] == 10


class TestLocalWorkerProvisioner:
    """Tests for LocalWorkerProvisioner."""

    @pytest.fixture
    def provisioner(self):
        return LocalWorkerProvisioner()

    @pytest.mark.asyncio
    async def test_provision_worker(self, provisioner):
        """Test provisioning a local worker."""
        resources = WorkerResources(cpu_cores=2)

        worker = await provisioner.provision("worker_1", resources)

        assert worker is not None
        assert worker.worker_type == WorkerType.LOCAL
        assert worker.status == WorkerStatus.IDLE

    @pytest.mark.asyncio
    async def test_terminate_worker(self, provisioner):
        """Test terminating a worker."""
        resources = WorkerResources()
        worker = await provisioner.provision("worker_1", resources)

        result = await provisioner.terminate(worker)

        assert result is True
        assert worker.status == WorkerStatus.TERMINATED

    @pytest.mark.asyncio
    async def test_health_check(self, provisioner):
        """Test worker health check."""
        resources = WorkerResources()
        worker = await provisioner.provision("worker_1", resources)

        is_healthy = await provisioner.health_check(worker)

        assert is_healthy is True


class TestWorkerPool:
    """Tests for WorkerPool."""

    @pytest.fixture
    def config(self):
        return PoolConfig(
            min_workers=0,
            max_workers=5,
            scale_up_threshold=0.8,
            scale_down_threshold=0.2,
        )

    @pytest_asyncio.fixture
    async def pool(self, config):
        return WorkerPool(config=config)

    @pytest.mark.asyncio
    async def test_pool_start_stop(self, pool):
        """Test pool start and stop."""
        await pool.start()

        assert pool._running

        await pool.stop()

        assert not pool._running

    @pytest.mark.asyncio
    async def test_worker_count(self, pool):
        """Test worker count property."""
        await pool.start()

        try:
            count = pool.worker_count
            assert count >= 0
        finally:
            await pool.stop()

    @pytest.mark.asyncio
    async def test_utilization(self, pool):
        """Test pool utilization."""
        await pool.start()

        try:
            util = pool.utilization
            assert 0.0 <= util <= 1.0
        finally:
            await pool.stop()

    @pytest.mark.asyncio
    async def test_available_workers(self, pool):
        """Test getting available workers."""
        await pool.start()

        try:
            available = pool.available_workers
            assert isinstance(available, list)
        finally:
            await pool.stop()


class TestWorkerPoolManager:
    """Tests for WorkerPoolManager."""

    @pytest_asyncio.fixture
    async def manager(self):
        return WorkerPoolManager()

    @pytest.mark.asyncio
    async def test_add_pool(self, manager):
        """Test adding a pool."""
        pool = WorkerPool()
        manager.add_pool("my_pool", pool)

        retrieved = manager.get_pool("my_pool")
        assert retrieved is pool

    @pytest.mark.asyncio
    async def test_get_pool(self, manager):
        """Test getting a pool."""
        pool = WorkerPool()
        manager.add_pool("test_pool", pool)

        retrieved = manager.get_pool("test_pool")

        assert retrieved is not None

    @pytest.mark.asyncio
    async def test_get_nonexistent_pool(self, manager):
        """Test getting nonexistent pool."""
        pool = manager.get_pool("nonexistent")

        assert pool is None

    @pytest.mark.asyncio
    async def test_select_pool(self, manager):
        """Test pool selection."""
        default_pool = WorkerPool()
        manager.add_pool("default", default_pool, default=True)

        selected = manager.select_pool(cpu=1.0, memory_gb=2.0)

        assert selected is default_pool

    @pytest.mark.asyncio
    async def test_start_stop_all(self, manager):
        """Test starting and stopping all pools."""
        pool1 = WorkerPool(config=PoolConfig(min_workers=0))
        pool2 = WorkerPool(config=PoolConfig(min_workers=0))
        manager.add_pool("pool_1", pool1)
        manager.add_pool("pool_2", pool2)

        await manager.start_all()

        assert pool1._running
        assert pool2._running

        await manager.stop_all()

        assert not pool1._running
        assert not pool2._running


class TestWorkerPoolSingleton:
    """Tests for worker pool singleton."""

    @pytest.mark.asyncio
    async def test_get_worker_pool(self):
        """Test singleton pattern."""
        pool1 = get_worker_pool()
        pool2 = get_worker_pool()

        assert pool1 is pool2
