"""Unit tests for the inbox watcher system."""

from __future__ import annotations

import pytest
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

from vastian.tasks.inbox import InboxWatcher


class MockOrchestrator:
    """Minimal mock orchestrator for inbox watcher tests."""

    def __init__(self, temp_dir: Path) -> None:
        self.task_queue = MagicMock()
        self.task_queue.enqueue = MagicMock(return_value=MagicMock(task_id="test-123"))
        self.task_queue.add_notification = MagicMock()
        self.task_queue.mark_running = MagicMock()
        self.task_queue.mark_completed = MagicMock()
        self.task_queue.mark_failed = MagicMock()
        self.task_queue.save_delegation_report = MagicMock()

        self.registry = MagicMock()
        mock_agent = MagicMock()
        mock_agent._network_roster = "- charles: Executive Assistant\n- finley: Finance Advisor"
        mock_agent.config = MagicMock()
        mock_agent.config.model_override = None
        self.registry.get_agent = MagicMock(return_value=mock_agent)
        self.registry.all_configs = []

        self.llm = AsyncMock()
        self._get_llm_for_agent = MagicMock(return_value=self.llm)
        self._resolve_model_for_agent = MagicMock(return_value="gpt-4o-mini")
        self._run_agent_with_tools = AsyncMock(return_value="Agent completed work")
        self.current_user_id = "default"


class TestInboxWatcherInit:
    """Tests for InboxWatcher initialization."""

    def test_creates_directories(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)
        assert watcher.inbox_dir.exists()
        assert watcher.intake_dir.exists()
        assert watcher.complete_dir.exists()

    def test_directory_paths(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)
        assert watcher.inbox_dir == temp_dir / "saved_prompts" / "inbox"
        assert watcher.intake_dir == temp_dir / "saved_prompts" / "intake"
        assert watcher.complete_dir == temp_dir / "saved_prompts" / "complete"


class TestInboxScan:
    """Tests for inbox file detection and processing."""

    @pytest.mark.asyncio
    async def test_scan_finds_txt_files(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        # Create a test file in inbox
        test_file = watcher.inbox_dir / "update.txt"
        test_file.write_text("I got a raise today! New salary is $130k.", encoding="utf-8")

        # Mock the processing to just verify detection
        watcher._process_inbox_file = AsyncMock()
        await watcher._scan_inbox()

        watcher._process_inbox_file.assert_called_once()
        call_arg = watcher._process_inbox_file.call_args[0][0]
        assert call_arg.name == "update.txt"

    @pytest.mark.asyncio
    async def test_scan_ignores_non_txt(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        (watcher.inbox_dir / "notes.md").write_text("# Notes", encoding="utf-8")

        watcher._process_inbox_file = AsyncMock()
        await watcher._scan_inbox()

        watcher._process_inbox_file.assert_not_called()

    @pytest.mark.asyncio
    async def test_scan_skips_short_files(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        test_file = watcher.inbox_dir / "tiny.txt"
        test_file.write_text("hi", encoding="utf-8")

        await watcher._scan_inbox()
        # File should still be in inbox (not moved to intake)
        assert test_file.exists()

    @pytest.mark.asyncio
    async def test_file_moves_to_intake(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        test_file = watcher.inbox_dir / "update.txt"
        test_file.write_text(
            "Big career update: just signed a full-time offer at Acme Corp.",
            encoding="utf-8",
        )

        # Mock execute to prevent full pipeline
        watcher._execute_inbox_task = AsyncMock()
        await watcher._process_inbox_file(test_file)

        # File should have moved from inbox to intake
        assert not test_file.exists()
        assert (watcher.intake_dir / "update.txt").exists()


class TestCharlesTriage:
    """Tests for the Charles triage step of inbox processing."""

    @pytest.mark.asyncio
    async def test_triage_returns_assignments(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        # Mock LLM to return valid JSON assignments
        orch.llm.generate = AsyncMock(
            return_value='[{"agent_id": "finley", "task_description": "Update budget"}]'
        )

        result = await watcher._charles_triage("task-123", "I got a raise to $130k")
        assert len(result) == 1
        assert result[0]["agent_id"] == "finley"

    @pytest.mark.asyncio
    async def test_triage_handles_empty_response(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        orch.llm.generate = AsyncMock(return_value="[]")
        result = await watcher._charles_triage("task-123", "Nothing specific")
        assert result == []

    @pytest.mark.asyncio
    async def test_triage_handles_invalid_json(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        orch.llm.generate = AsyncMock(return_value="This is not JSON")
        result = await watcher._charles_triage("task-123", "Some update")
        assert result == []

    @pytest.mark.asyncio
    async def test_triage_handles_code_block_json(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        response = '```json\n[{"agent_id": "liam", "task_description": "Update sprint"}]\n```'
        orch.llm.generate = AsyncMock(return_value=response)

        result = await watcher._charles_triage("task-123", "Sprint update")
        assert len(result) == 1
        assert result[0]["agent_id"] == "liam"


class TestDelegationExecution:
    """Tests for the delegation execution phase."""

    @pytest.mark.asyncio
    async def test_execute_successful_delegation(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        assignments = [{"agent_id": "finley", "task_description": "Update budget"}]
        results = await watcher._execute_delegations("task-123", assignments, "I got a raise")

        assert len(results) == 1
        assert results[0]["status"] == "completed"
        assert results[0]["agent_id"] == "finley"

    @pytest.mark.asyncio
    async def test_execute_with_unknown_agent(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        orch.registry.get_agent = MagicMock(return_value=None)
        watcher = InboxWatcher(orch, temp_dir)

        assignments = [{"agent_id": "nobody", "task_description": "Do something"}]
        results = await watcher._execute_delegations("task-123", assignments, "Update")

        assert len(results) == 1
        assert results[0]["status"] == "failed"
        assert "not found" in results[0]["full_result"]

    @pytest.mark.asyncio
    async def test_execute_parallel_delegations(self, temp_dir: Path) -> None:
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        assignments = [
            {"agent_id": "finley", "task_description": "Update budget"},
            {"agent_id": "liam", "task_description": "Update tasks"},
            {"agent_id": "dominic", "task_description": "Update roadmap"},
        ]
        results = await watcher._execute_delegations("task-123", assignments, "Big update")
        assert len(results) == 3
        assert all(r["status"] == "completed" for r in results)
