"""End-to-end tests — full pipeline scenarios without real LLM calls."""

from __future__ import annotations

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

from vastian.tasks.queue import TaskQueue
from vastian.tasks.inbox import InboxWatcher


class MockRegistry:
    """Mock registry that mimics the real one."""

    def __init__(self) -> None:
        self.all_configs: list = []

    def get_agent(self, agent_id: str):
        mock = MagicMock()
        mock.agent_id = agent_id
        mock._network_roster = "- charles: Executive Assistant\n- finley: Finance"
        mock.config = MagicMock()
        mock.config.model_override = None
        return mock


class MockOrchestrator:
    """Full mock orchestrator for e2e tests."""

    def __init__(self, temp_dir: Path) -> None:
        self.task_queue = TaskQueue(temp_dir / "e2e_tasks.db")
        self.task_queue.initialize()

        self.registry = MockRegistry()
        self.llm = AsyncMock()
        self._get_llm_for_agent = MagicMock(return_value=self.llm)
        self._resolve_model_for_agent = MagicMock(return_value=None)
        self._run_agent_with_tools = AsyncMock(
            return_value="Agent completed the work successfully."
        )

        # settings stub — inbox.py reads orchestrator.settings.docs_dir
        self.settings = MagicMock()
        self.settings.docs_dir = temp_dir / "docs"


class TestInboxE2E:
    """End-to-end: Full inbox processing pipeline."""

    @pytest.mark.skip(reason="Flaky: requires full orchestrator mock chain")
    @pytest.mark.asyncio
    async def test_full_inbox_pipeline(self, temp_dir: Path) -> None:
        """Simulate: user drops a file → Charles triages → agents execute → file moves to complete."""
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        # Mock Charles triage: returns assignments for finley and liam
        orch.llm.generate = AsyncMock(
            side_effect=[
                # Charles triage
                '[{"agent_id": "finley", "task_description": "Update salary to 130k"}, '
                '{"agent_id": "liam", "task_description": "Add salary task to sprint"}]',
                # Theo review
                "NO_GAPS",
                # Orion review
                "NO_GAPS",
            ]
        )

        # Create inbox file
        inbox_file = watcher.inbox_dir / "salary_update.txt"
        inbox_file.write_text(
            "Great news! I got a raise today. New salary is $130k starting March 1.",
            encoding="utf-8",
        )

        # Process the file
        await watcher._process_inbox_file(inbox_file)

        # Wait a bit for the async task to complete
        await asyncio.sleep(0.5)

        # File should have moved from inbox → intake → complete
        assert not inbox_file.exists(), "File should no longer be in inbox"
        assert not (watcher.intake_dir / "salary_update.txt").exists(), (
            "File should not be in intake"
        )

        complete_files = list(watcher.complete_dir.glob("*salary_update.txt"))
        assert len(complete_files) == 1, "File should be in complete"

        # Reports should have been saved
        reports = orch.task_queue.get_reports_for_agent("finley")
        assert len(reports) >= 1

    @pytest.mark.asyncio
    async def test_inbox_failed_agent_still_completes(self, temp_dir: Path) -> None:
        """If one agent fails, the rest still complete and the file moves to complete."""
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        orch.llm.generate = AsyncMock(
            side_effect=[
                '[{"agent_id": "finley", "task_description": "Update budget"}, '
                '{"agent_id": "badagent", "task_description": "Do something"}]',
                "NO_GAPS",
                "NO_GAPS",
            ]
        )

        # Make "badagent" not found
        original_get = orch.registry.get_agent

        def selective_get(agent_id):
            if agent_id == "badagent":
                return None
            return original_get(agent_id)

        orch.registry.get_agent = selective_get

        inbox_file = watcher.inbox_dir / "mixed_update.txt"
        inbox_file.write_text(
            "I need budget updates and some impossible task done.",
            encoding="utf-8",
        )

        await watcher._process_inbox_file(inbox_file)
        await asyncio.sleep(0.5)

        # File should still complete
        complete_files = list(watcher.complete_dir.glob("*mixed_update.txt"))
        assert len(complete_files) == 1

    @pytest.mark.asyncio
    async def test_inbox_multiple_files_processed_in_order(self, temp_dir: Path) -> None:
        """Multiple files in inbox are processed in modification-time order."""
        orch = MockOrchestrator(temp_dir)
        watcher = InboxWatcher(orch, temp_dir)

        processed_files = []
        original_process = watcher._process_inbox_file

        async def tracking_process(file_path):
            processed_files.append(file_path.name)
            # Don't actually process
            file_path.unlink()

        watcher._process_inbox_file = tracking_process

        # Create files
        import time

        f1 = watcher.inbox_dir / "first.txt"
        f1.write_text("First update paragraph for testing.", encoding="utf-8")
        time.sleep(0.1)
        f2 = watcher.inbox_dir / "second.txt"
        f2.write_text("Second update paragraph for testing.", encoding="utf-8")

        await watcher._scan_inbox()

        assert processed_files == ["first.txt", "second.txt"]


class TestDelegationReportE2E:
    """End-to-end: Delegation reports stored and retrievable."""

    def test_full_report_lifecycle(self, temp_dir: Path) -> None:
        tq = TaskQueue(temp_dir / "report_e2e.db")
        tq.initialize()

        # Simulate a batch delegation
        batch = tq.enqueue(
            "batch_delegation",
            "liam",
            "Sprint kickoff delegation",
            {
                "delegations": [
                    {"agent_id": "finley", "prompt": "Budget review"},
                    {"agent_id": "henry", "prompt": "Architecture review"},
                ]
            },
        )

        # Simulate agents completing work
        tq.save_delegation_report(
            parent_task_id=batch.task_id,
            agent_id="finley",
            description="Budget review",
            full_result="I reviewed the budget. Current savings rate is 15%. Recommend increasing to 20%.",
            tools_used=["read_document", "write_document"],
            documents_written=["finley/financial-projections.md"],
        )
        tq.save_delegation_report(
            parent_task_id=batch.task_id,
            agent_id="henry",
            description="Architecture review",
            full_result="Architecture looks solid. Suggest adding Redis cache layer for vector store.",
            tools_used=["search_codebase", "read_document"],
        )

        # Retrieve reports
        reports = tq.get_reports_for_task(batch.task_id)
        assert len(reports) == 2

        finley_report = next(r for r in reports if r["agent_id"] == "finley")
        assert "15%" in finley_report["full_result"]
        assert "write_document" in finley_report["tools_used"]
        assert "finley/financial-projections.md" in finley_report["documents_written"]

        henry_report = next(r for r in reports if r["agent_id"] == "henry")
        assert "Redis" in henry_report["full_result"]

        tq.close()
