"""Tests for brain.worker — DistributedWorker capability detection + task execution."""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from brain.worker import DistributedWorker


class TestWorkerInit:
    """Test worker initialization and capability detection."""

    @patch("brain.worker.DistributedWorker._detect_capabilities")
    def test_init_defaults(self, mock_caps):
        mock_caps.return_value = ["general"]
        worker = DistributedWorker()
        assert worker.running is False
        assert worker.node_id  # non-empty
        assert "general" in worker.capabilities

    @patch("brain.worker.DistributedWorker._detect_capabilities")
    def test_custom_brain_client(self, mock_caps):
        mock_caps.return_value = ["general"]
        mock_client = MagicMock()
        worker = DistributedWorker(brain_client=mock_client)
        assert worker.brain is mock_client

    @patch("brain.worker.DistributedWorker._detect_capabilities")
    def test_default_handlers_registered(self, mock_caps):
        mock_caps.return_value = ["general"]
        worker = DistributedWorker()
        assert "shell" in worker._handlers
        assert "creative" in worker._handlers
        assert "code" in worker._handlers
        assert "research" in worker._handlers
        assert "general" in worker._handlers


class TestCapabilityDetection:
    """Test individual capability detection methods."""

    @patch("brain.worker.DistributedWorker._detect_capabilities", return_value=["general"])
    def _make_worker(self, mock_caps):
        return DistributedWorker()

    @patch("brain.worker.subprocess.run")
    def test_has_gpu_nvidia(self, mock_run):
        worker = self._make_worker()
        mock_run.return_value = MagicMock(returncode=0)
        assert worker._has_gpu() is True

    @patch("brain.worker.subprocess.run", side_effect=OSError("no nvidia-smi"))
    @patch("brain.worker.os.path.exists", return_value=False)
    @patch("brain.worker.os.uname")
    def test_has_gpu_no_gpu(self, mock_uname, mock_exists, mock_run):
        worker = self._make_worker()
        mock_uname.return_value = MagicMock(sysname="Linux")
        assert worker._has_gpu() is False

    @patch("brain.worker.subprocess.run")
    def test_has_internet_true(self, mock_run):
        worker = self._make_worker()
        mock_run.return_value = MagicMock(returncode=0)
        assert worker._has_internet() is True

    @patch("brain.worker.subprocess.run", side_effect=OSError("no curl"))
    def test_has_internet_no_curl(self, mock_run):
        worker = self._make_worker()
        assert worker._has_internet() is False

    @patch("brain.worker.subprocess.run")
    def test_has_command_true(self, mock_run):
        worker = self._make_worker()
        mock_run.return_value = MagicMock(returncode=0)
        assert worker._has_command("docker") is True

    @patch("brain.worker.subprocess.run")
    def test_has_command_false(self, mock_run):
        worker = self._make_worker()
        mock_run.return_value = MagicMock(returncode=1)
        assert worker._has_command("nonexistent") is False

    @patch("brain.worker.subprocess.run", side_effect=OSError)
    def test_has_command_oserror(self, mock_run):
        worker = self._make_worker()
        assert worker._has_command("anything") is False


class TestRegisterHandler:
    """Test custom handler registration."""

    @patch("brain.worker.DistributedWorker._detect_capabilities", return_value=["general"])
    def test_register_adds_handler_and_capability(self, mock_caps):
        worker = DistributedWorker()

        async def custom_handler(content):
            return "custom result"

        worker.register_handler("custom_type", custom_handler)
        assert "custom_type" in worker._handlers
        assert "custom_type" in worker.capabilities


class TestTaskHandlers:
    """Test individual task handlers."""

    @patch("brain.worker.DistributedWorker._detect_capabilities", return_value=["general"])
    def _make_worker(self, mock_caps):
        worker = DistributedWorker()
        worker.brain = MagicMock()
        worker.brain.remember = AsyncMock()
        worker.brain.complete_task = AsyncMock()
        return worker

    async def test_handle_code(self):
        worker = self._make_worker()
        result = await worker._handle_code("write a function")
        assert "write a function" in result

    async def test_handle_research_no_internet(self):
        worker = self._make_worker()
        worker.capabilities = ["general"]  # no "web"
        with pytest.raises(Exception, match="Internet not available"):
            await worker._handle_research("search query")

    async def test_handle_creative_no_gpu(self):
        worker = self._make_worker()
        worker.capabilities = ["general"]  # no "gpu"
        with pytest.raises(Exception, match="GPU not available"):
            await worker._handle_creative("make an image")

    async def test_handle_general_no_ollama(self):
        worker = self._make_worker()
        worker.capabilities = ["general"]  # but not "ollama"
        # Remove "ollama" if present
        if "ollama" in worker.capabilities:
            worker.capabilities.remove("ollama")
        with pytest.raises(Exception, match="Ollama not available"):
            await worker._handle_general("answer this")


class TestExecuteTask:
    """Test execute_task orchestration."""

    @patch("brain.worker.DistributedWorker._detect_capabilities", return_value=["general"])
    def _make_worker(self, mock_caps):
        worker = DistributedWorker()
        worker.brain = MagicMock()
        worker.brain.complete_task = AsyncMock()
        return worker

    async def test_execute_task_success(self):
        worker = self._make_worker()

        async def fake_handler(content):
            return "result"

        worker._handlers["test_type"] = fake_handler

        result = await worker.execute_task(
            {
                "id": 1,
                "type": "test_type",
                "content": "do it",
            }
        )
        assert result["success"] is True
        assert result["result"] == "result"
        worker.brain.complete_task.assert_awaited_once_with(1, success=True, result="result")

    async def test_execute_task_failure(self):
        worker = self._make_worker()

        async def failing_handler(content):
            raise RuntimeError("oops")

        worker._handlers["fail_type"] = failing_handler

        result = await worker.execute_task(
            {
                "id": 2,
                "type": "fail_type",
                "content": "fail",
            }
        )
        assert result["success"] is False
        assert "oops" in result["error"]
        worker.brain.complete_task.assert_awaited_once()

    async def test_execute_task_falls_back_to_general(self):
        worker = self._make_worker()

        async def general_handler(content):
            return "general result"

        worker._handlers["general"] = general_handler

        result = await worker.execute_task(
            {
                "id": 3,
                "type": "unknown_type",
                "content": "something",
            }
        )
        assert result["success"] is True
        assert result["result"] == "general result"


class TestRunOnce:
    """Test run_once loop iteration."""

    @patch("brain.worker.DistributedWorker._detect_capabilities", return_value=["general"])
    def _make_worker(self, mock_caps):
        worker = DistributedWorker()
        worker.brain = MagicMock()
        worker.brain.claim_task = AsyncMock()
        worker.brain.complete_task = AsyncMock()
        return worker

    async def test_run_once_no_tasks(self):
        worker = self._make_worker()
        worker.brain.claim_task.return_value = None

        had_work = await worker.run_once()
        assert had_work is False

    async def test_run_once_with_task(self):
        worker = self._make_worker()
        worker.brain.claim_task.return_value = {"id": 1, "type": "code", "content": "write code"}

        had_work = await worker.run_once()
        assert had_work is True


class TestStopWorker:
    """Test stop method."""

    @patch("brain.worker.DistributedWorker._detect_capabilities", return_value=["general"])
    def test_stop_sets_running_false(self, mock_caps):
        worker = DistributedWorker()
        worker.running = True
        worker.stop()
        assert worker.running is False
