"""Tests for unified task management interface.

TDD: These tests are written first (RED phase).
"""

from abc import ABC
from unittest.mock import MagicMock, patch

import pytest


class TestTaskStatus:
    """Tests for TaskStatus dataclass."""

    def test_task_status_basic_fields(self) -> None:
        """TaskStatus has all required fields."""
        from backend.web.core.task_interface import TaskStatus

        status = TaskStatus(
            name="test_runner",
            is_running=True,
            pending_count=5,
            processing_count=2,
        )
        assert status.name == "test_runner"
        assert status.is_running is True
        assert status.pending_count == 5
        assert status.processing_count == 2

    def test_task_status_with_error(self) -> None:
        """TaskStatus can include optional error message."""
        from backend.web.core.task_interface import TaskStatus

        status = TaskStatus(
            name="failed_runner",
            is_running=False,
            pending_count=0,
            processing_count=0,
            error="Connection failed",
        )
        assert status.error == "Connection failed"

    def test_task_status_default_error_is_none(self) -> None:
        """TaskStatus error defaults to None."""
        from backend.web.core.task_interface import TaskStatus

        status = TaskStatus(
            name="test",
            is_running=True,
            pending_count=0,
            processing_count=0,
        )
        assert status.error is None

    def test_task_status_is_frozen(self) -> None:
        """TaskStatus is immutable (frozen dataclass)."""
        from backend.web.core.task_interface import TaskStatus

        status = TaskStatus(
            name="test",
            is_running=True,
            pending_count=0,
            processing_count=0,
        )
        with pytest.raises(AttributeError):
            status.name = "changed"  # type: ignore[misc]


class TestTaskRunnerInterface:
    """Tests for TaskRunner abstract base class."""

    def test_cannot_instantiate_directly(self) -> None:
        """TaskRunner is abstract and cannot be instantiated."""
        from backend.web.core.task_interface import TaskRunner

        with pytest.raises(TypeError):
            TaskRunner()  # type: ignore[abstract]

    def test_is_abstract_base_class(self) -> None:
        """TaskRunner inherits from ABC."""
        from backend.web.core.task_interface import TaskRunner

        assert issubclass(TaskRunner, ABC)

    def test_subclass_missing_name_cannot_instantiate(self) -> None:
        """Subclass without name property cannot be instantiated."""
        from backend.web.core.task_interface import TaskRunner, TaskStatus

        class MissingName(TaskRunner):
            def start(self) -> None:
                pass

            def stop(self, timeout: float | None = None) -> None:
                pass

            @property
            def is_running(self) -> bool:
                return False

            def get_status(self) -> TaskStatus:
                return TaskStatus("", False, 0, 0)

        with pytest.raises(TypeError):
            MissingName()  # type: ignore[abstract]

    def test_subclass_missing_start_cannot_instantiate(self) -> None:
        """Subclass without start method cannot be instantiated."""
        from backend.web.core.task_interface import TaskRunner, TaskStatus

        class MissingStart(TaskRunner):
            @property
            def name(self) -> str:
                return "test"

            def stop(self, timeout: float | None = None) -> None:
                pass

            @property
            def is_running(self) -> bool:
                return False

            def get_status(self) -> TaskStatus:
                return TaskStatus("", False, 0, 0)

        with pytest.raises(TypeError):
            MissingStart()  # type: ignore[abstract]

    def test_subclass_missing_stop_cannot_instantiate(self) -> None:
        """Subclass without stop method cannot be instantiated."""
        from backend.web.core.task_interface import TaskRunner, TaskStatus

        class MissingStop(TaskRunner):
            @property
            def name(self) -> str:
                return "test"

            def start(self) -> None:
                pass

            @property
            def is_running(self) -> bool:
                return False

            def get_status(self) -> TaskStatus:
                return TaskStatus("", False, 0, 0)

        with pytest.raises(TypeError):
            MissingStop()  # type: ignore[abstract]

    def test_subclass_missing_is_running_cannot_instantiate(self) -> None:
        """Subclass without is_running property cannot be instantiated."""
        from backend.web.core.task_interface import TaskRunner, TaskStatus

        class MissingIsRunning(TaskRunner):
            @property
            def name(self) -> str:
                return "test"

            def start(self) -> None:
                pass

            def stop(self, timeout: float | None = None) -> None:
                pass

            def get_status(self) -> TaskStatus:
                return TaskStatus("", False, 0, 0)

        with pytest.raises(TypeError):
            MissingIsRunning()  # type: ignore[abstract]

    def test_subclass_missing_get_status_cannot_instantiate(self) -> None:
        """Subclass without get_status method cannot be instantiated."""
        from backend.web.core.task_interface import TaskRunner

        class MissingGetStatus(TaskRunner):
            @property
            def name(self) -> str:
                return "test"

            def start(self) -> None:
                pass

            def stop(self, timeout: float | None = None) -> None:
                pass

            @property
            def is_running(self) -> bool:
                return False

        with pytest.raises(TypeError):
            MissingGetStatus()  # type: ignore[abstract]

    def test_complete_subclass_can_instantiate(self) -> None:
        """Complete subclass implementing all methods can be instantiated."""
        from backend.web.core.task_interface import TaskRunner, TaskStatus

        class CompleteRunner(TaskRunner):
            def __init__(self) -> None:
                self._running = False

            @property
            def name(self) -> str:
                return "complete_runner"

            def start(self) -> None:
                self._running = True

            def stop(self, timeout: float | None = None) -> None:
                self._running = False

            @property
            def is_running(self) -> bool:
                return self._running

            def get_status(self) -> TaskStatus:
                return TaskStatus(
                    name=self.name,
                    is_running=self._running,
                    pending_count=0,
                    processing_count=0,
                )

        runner = CompleteRunner()
        assert runner.name == "complete_runner"
        assert runner.is_running is False

        runner.start()
        assert runner.is_running is True

        status = runner.get_status()
        assert status.name == "complete_runner"
        assert status.is_running is True

        runner.stop()
        assert runner.is_running is False


class TestTaskManager:
    """Tests for TaskManager facade."""

    def test_register_runner(self) -> None:
        """Can register a task runner."""
        from backend.web.core.task_interface import TaskManager, TaskRunner, TaskStatus

        class MockRunner(TaskRunner):
            @property
            def name(self) -> str:
                return "mock"

            def start(self) -> None:
                pass

            def stop(self, timeout: float | None = None) -> None:
                pass

            @property
            def is_running(self) -> bool:
                return False

            def get_status(self) -> TaskStatus:
                return TaskStatus("mock", False, 0, 0)

        manager = TaskManager()
        runner = MockRunner()
        manager.register(runner)

        assert manager.get_runner("mock") is runner

    def test_get_runner_returns_none_for_unknown(self) -> None:
        """get_runner returns None for unknown runner name."""
        from backend.web.core.task_interface import TaskManager

        manager = TaskManager()
        assert manager.get_runner("unknown") is None

    def test_start_all_runners(self) -> None:
        """start_all starts all registered runners."""
        from backend.web.core.task_interface import TaskManager, TaskRunner, TaskStatus

        class MockRunner(TaskRunner):
            def __init__(self, runner_name: str) -> None:
                self._name = runner_name
                self._running = False

            @property
            def name(self) -> str:
                return self._name

            def start(self) -> None:
                self._running = True

            def stop(self, timeout: float | None = None) -> None:
                self._running = False

            @property
            def is_running(self) -> bool:
                return self._running

            def get_status(self) -> TaskStatus:
                return TaskStatus(self._name, self._running, 0, 0)

        manager = TaskManager()
        runner1 = MockRunner("runner1")
        runner2 = MockRunner("runner2")
        manager.register(runner1)
        manager.register(runner2)

        assert runner1.is_running is False
        assert runner2.is_running is False

        manager.start_all()

        assert runner1.is_running is True
        assert runner2.is_running is True

    def test_stop_all_runners(self) -> None:
        """stop_all stops all registered runners."""
        from backend.web.core.task_interface import TaskManager, TaskRunner, TaskStatus

        class MockRunner(TaskRunner):
            def __init__(self, runner_name: str) -> None:
                self._name = runner_name
                self._running = True

            @property
            def name(self) -> str:
                return self._name

            def start(self) -> None:
                self._running = True

            def stop(self, timeout: float | None = None) -> None:
                self._running = False

            @property
            def is_running(self) -> bool:
                return self._running

            def get_status(self) -> TaskStatus:
                return TaskStatus(self._name, self._running, 0, 0)

        manager = TaskManager()
        runner1 = MockRunner("runner1")
        runner2 = MockRunner("runner2")
        manager.register(runner1)
        manager.register(runner2)

        assert runner1.is_running is True
        assert runner2.is_running is True

        manager.stop_all()

        assert runner1.is_running is False
        assert runner2.is_running is False

    def test_get_all_status(self) -> None:
        """get_all_status returns status of all runners."""
        from backend.web.core.task_interface import TaskManager, TaskRunner, TaskStatus

        class MockRunner(TaskRunner):
            def __init__(self, runner_name: str, pending: int) -> None:
                self._name = runner_name
                self._pending = pending

            @property
            def name(self) -> str:
                return self._name

            def start(self) -> None:
                pass

            def stop(self, timeout: float | None = None) -> None:
                pass

            @property
            def is_running(self) -> bool:
                return True

            def get_status(self) -> TaskStatus:
                return TaskStatus(self._name, True, self._pending, 0)

        manager = TaskManager()
        manager.register(MockRunner("runner1", 5))
        manager.register(MockRunner("runner2", 10))

        all_status = manager.get_all_status()

        assert len(all_status) == 2
        assert all_status["runner1"].pending_count == 5
        assert all_status["runner2"].pending_count == 10

    def test_get_all_status_empty_when_no_runners(self) -> None:
        """get_all_status returns empty dict when no runners registered."""
        from backend.web.core.task_interface import TaskManager

        manager = TaskManager()
        assert manager.get_all_status() == {}

    def test_runner_names_property(self) -> None:
        """runner_names returns list of all registered runner names."""
        from backend.web.core.task_interface import TaskManager, TaskRunner, TaskStatus

        class MockRunner(TaskRunner):
            def __init__(self, runner_name: str) -> None:
                self._name = runner_name

            @property
            def name(self) -> str:
                return self._name

            def start(self) -> None:
                pass

            def stop(self, timeout: float | None = None) -> None:
                pass

            @property
            def is_running(self) -> bool:
                return False

            def get_status(self) -> TaskStatus:
                return TaskStatus(self._name, False, 0, 0)

        manager = TaskManager()
        manager.register(MockRunner("alpha"))
        manager.register(MockRunner("beta"))

        names = manager.runner_names
        assert set(names) == {"alpha", "beta"}

    def test_stop_all_with_timeout_distribution(self) -> None:
        """stop_all distributes timeout across runners."""
        from backend.web.core.task_interface import TaskManager, TaskRunner, TaskStatus

        received_timeouts: list[float | None] = []

        class MockRunner(TaskRunner):
            def __init__(self, runner_name: str) -> None:
                self._name = runner_name

            @property
            def name(self) -> str:
                return self._name

            def start(self) -> None:
                pass

            def stop(self, timeout: float | None = None) -> None:
                received_timeouts.append(timeout)

            @property
            def is_running(self) -> bool:
                return False

            def get_status(self) -> TaskStatus:
                return TaskStatus(self._name, False, 0, 0)

        manager = TaskManager()
        manager.register(MockRunner("r1"))
        manager.register(MockRunner("r2"))

        manager.stop_all(timeout=20.0)

        # Timeout should be distributed (20 / 2 = 10 each)
        assert len(received_timeouts) == 2
        assert all(t == 10.0 for t in received_timeouts)

    def test_start_all_skips_runners_requiring_arguments(self) -> None:
        """start_all skips runners that require arguments."""
        from backend.web.core.task_interface import TaskManager, TaskRunner, TaskStatus

        no_args_started = []
        with_args_started = []

        class NoArgsRunner(TaskRunner):
            @property
            def name(self) -> str:
                return "no_args"

            def start(self) -> None:
                no_args_started.append(True)

            def stop(self, timeout: float | None = None) -> None:
                pass

            @property
            def is_running(self) -> bool:
                return False

            def get_status(self) -> TaskStatus:
                return TaskStatus("no_args", False, 0, 0)

        class RequiresArgsRunner(TaskRunner):
            @property
            def name(self) -> str:
                return "requires_args"

            def start(self, handler: object) -> None:  # type: ignore[override]
                # This runner requires an argument
                with_args_started.append(True)

            def stop(self, timeout: float | None = None) -> None:
                pass

            @property
            def is_running(self) -> bool:
                return False

            def get_status(self) -> TaskStatus:
                return TaskStatus("requires_args", False, 0, 0)

        manager = TaskManager()
        manager.register(NoArgsRunner())
        manager.register(RequiresArgsRunner())

        # start_all should start no_args runner but skip requires_args
        manager.start_all()

        assert len(no_args_started) == 1
        assert len(with_args_started) == 0  # Skipped due to TypeError

    def test_stop_all_with_no_runners(self) -> None:
        """stop_all does nothing when no runners registered."""
        from backend.web.core.task_interface import TaskManager

        manager = TaskManager()
        # Should not raise any exception
        manager.stop_all()
        # Just verify it returns without error
        assert manager.runner_names == []


class TestTrainingSchedulerInterface:
    """Tests for TrainingScheduler implementing TaskRunner."""

    def test_training_scheduler_is_task_runner(self) -> None:
        """TrainingScheduler inherits from TaskRunner."""
        from backend.web.core.scheduler import TrainingScheduler
        from backend.web.core.task_interface import TaskRunner

        scheduler = TrainingScheduler()
        assert isinstance(scheduler, TaskRunner)

    def test_training_scheduler_name(self) -> None:
        """TrainingScheduler has correct name."""
        from backend.web.core.scheduler import TrainingScheduler

        scheduler = TrainingScheduler()
        assert scheduler.name == "training_scheduler"

    def test_training_scheduler_get_status(self) -> None:
        """TrainingScheduler provides status via get_status."""
        from backend.web.core.scheduler import TrainingScheduler
        from backend.web.core.task_interface import TaskStatus

        scheduler = TrainingScheduler()
        # Mock the training tasks repository
        mock_tasks = MagicMock()
        mock_tasks.get_pending.return_value = [MagicMock(), MagicMock()]
        scheduler._training_tasks = mock_tasks

        status = scheduler.get_status()

        assert isinstance(status, TaskStatus)
        assert status.name == "training_scheduler"
        assert status.is_running is False
        assert status.pending_count == 2


class TestAutoLabelSchedulerInterface:
    """Tests for AutoLabelScheduler implementing TaskRunner."""

    def test_autolabel_scheduler_is_task_runner(self) -> None:
        """AutoLabelScheduler inherits from TaskRunner."""
        from backend.web.core.autolabel_scheduler import AutoLabelScheduler
        from backend.web.core.task_interface import TaskRunner

        with patch("backend.web.core.autolabel_scheduler.get_storage_helper"):
            scheduler = AutoLabelScheduler()
            assert isinstance(scheduler, TaskRunner)

    def test_autolabel_scheduler_name(self) -> None:
        """AutoLabelScheduler has correct name."""
        from backend.web.core.autolabel_scheduler import AutoLabelScheduler

        with patch("backend.web.core.autolabel_scheduler.get_storage_helper"):
            scheduler = AutoLabelScheduler()
            assert scheduler.name == "autolabel_scheduler"

    def test_autolabel_scheduler_get_status(self) -> None:
        """AutoLabelScheduler provides status via get_status."""
        from backend.web.core.autolabel_scheduler import AutoLabelScheduler
        from backend.web.core.task_interface import TaskStatus

        with patch("backend.web.core.autolabel_scheduler.get_storage_helper"):
            with patch(
                "backend.web.core.autolabel_scheduler.get_pending_autolabel_documents"
            ) as mock_get:
                mock_get.return_value = [MagicMock(), MagicMock(), MagicMock()]

                scheduler = AutoLabelScheduler()
                status = scheduler.get_status()

                assert isinstance(status, TaskStatus)
                assert status.name == "autolabel_scheduler"
                assert status.is_running is False
                assert status.pending_count == 3


class TestAsyncTaskQueueInterface:
    """Tests for AsyncTaskQueue implementing TaskRunner."""

    def test_async_queue_is_task_runner(self) -> None:
        """AsyncTaskQueue inherits from TaskRunner."""
        from backend.web.workers.async_queue import AsyncTaskQueue
        from backend.web.core.task_interface import TaskRunner

        queue = AsyncTaskQueue()
        assert isinstance(queue, TaskRunner)

    def test_async_queue_name(self) -> None:
        """AsyncTaskQueue has correct name."""
        from backend.web.workers.async_queue import AsyncTaskQueue

        queue = AsyncTaskQueue()
        assert queue.name == "async_task_queue"

    def test_async_queue_get_status(self) -> None:
        """AsyncTaskQueue provides status via get_status."""
        from backend.web.workers.async_queue import AsyncTaskQueue
        from backend.web.core.task_interface import TaskStatus

        queue = AsyncTaskQueue()
        status = queue.get_status()

        assert isinstance(status, TaskStatus)
        assert status.name == "async_task_queue"
        assert status.is_running is False
        assert status.pending_count == 0
        assert status.processing_count == 0


class TestBatchTaskQueueInterface:
    """Tests for BatchTaskQueue implementing TaskRunner."""

    def test_batch_queue_is_task_runner(self) -> None:
        """BatchTaskQueue inherits from TaskRunner."""
        from backend.web.workers.batch_queue import BatchTaskQueue
        from backend.web.core.task_interface import TaskRunner

        queue = BatchTaskQueue()
        assert isinstance(queue, TaskRunner)

    def test_batch_queue_name(self) -> None:
        """BatchTaskQueue has correct name."""
        from backend.web.workers.batch_queue import BatchTaskQueue

        queue = BatchTaskQueue()
        assert queue.name == "batch_task_queue"

    def test_batch_queue_get_status(self) -> None:
        """BatchTaskQueue provides status via get_status."""
        from backend.web.workers.batch_queue import BatchTaskQueue
        from backend.web.core.task_interface import TaskStatus

        queue = BatchTaskQueue()
        status = queue.get_status()

        assert isinstance(status, TaskStatus)
        assert status.name == "batch_task_queue"
        assert status.is_running is False
        assert status.pending_count == 0
