"""
Unit tests for the agents module.

Tests for base classes, runner, and iterate-pr agent.
"""

import asyncio
import json
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch

import pytest

from persuade.agents.base import AgentPhaseResult, TwoPhaseAgent
from persuade.agents.runner import AgentRunner, cli_main


class MockAgent(TwoPhaseAgent):
    """Mock agent for testing."""

    def __init__(
        self,
        plan_success: bool = True,
        execute_success: bool = True,
        plan_messages: list[str] | None = None,
        execute_messages: list[str] | None = None,
    ):
        self.plan_success = plan_success
        self.execute_success = execute_success
        self.plan_messages = plan_messages or ["Plan completed"]
        self.execute_messages = execute_messages or ["Execution completed"]
        self.plan_called = False
        self.execute_called = False

    async def plan(self, **kwargs: Any) -> AgentPhaseResult:
        self.plan_called = True
        if self.plan_success:
            return AgentPhaseResult(
                success=True,
                data={"test": "data"},
                messages=self.plan_messages,
            )
        return AgentPhaseResult(
            success=False,
            error="Plan failed",
        )

    async def execute(self, plan_result: AgentPhaseResult, **kwargs: Any) -> AgentPhaseResult:
        self.execute_called = True
        if self.execute_success:
            return AgentPhaseResult(
                success=True,
                data={"executed": True},
                messages=self.execute_messages,
            )
        return AgentPhaseResult(
            success=False,
            error="Execution failed",
        )


class TestAgentPhaseResult:
    """Tests for AgentPhaseResult dataclass."""

    def test_success_result(self) -> None:
        """Test creating a successful result."""
        result = AgentPhaseResult(
            success=True,
            data={"key": "value"},
            messages=["Done"],
        )
        assert result.success is True
        assert result.data == {"key": "value"}
        assert result.messages == ["Done"]
        assert result.error is None

    def test_failure_result(self) -> None:
        """Test creating a failure result."""
        result = AgentPhaseResult(
            success=False,
            error="Something went wrong",
        )
        assert result.success is False
        assert result.error == "Something went wrong"
        assert result.data == {}
        assert result.messages == []

    def test_default_values(self) -> None:
        """Test default values for optional fields."""
        result = AgentPhaseResult(success=True)
        assert result.data == {}
        assert result.messages == []
        assert result.error is None


class TestTwoPhaseAgent:
    """Tests for TwoPhaseAgent base class."""

    def test_run_both_phases(self) -> None:
        """Test running both phases sequentially."""
        agent = MockAgent()
        result = asyncio.run(agent.run(auto_approve=True))

        assert agent.plan_called is True
        assert agent.execute_called is True
        assert result.success is True

    def test_plan_failure_stops_execution(self) -> None:
        """Test that plan failure prevents execution."""
        agent = MockAgent(plan_success=False)
        result = asyncio.run(agent.run(auto_approve=True))

        assert agent.plan_called is True
        assert agent.execute_called is False
        assert result.success is False
        assert result.error == "Plan failed"

    def test_execute_failure(self) -> None:
        """Test handling of execution failure."""
        agent = MockAgent(execute_success=False)
        result = asyncio.run(agent.run(auto_approve=True))

        assert agent.plan_called is True
        assert agent.execute_called is True
        assert result.success is False
        assert result.error == "Execution failed"


class TestAgentRunner:
    """Tests for AgentRunner class."""

    def test_run_sync_success(self) -> None:
        """Test synchronous run with success."""
        agent = MockAgent()
        result = AgentRunner.run_sync(agent, auto_approve=True)

        assert result.success is True
        assert agent.plan_called is True
        assert agent.execute_called is True

    def test_run_sync_plan_failure(self) -> None:
        """Test synchronous run with plan failure."""
        agent = MockAgent(plan_success=False)
        result = AgentRunner.run_sync(agent, auto_approve=True)

        assert result.success is False
        assert agent.execute_called is False

    def test_run_headless(self) -> None:
        """Test headless run (always auto-approves)."""
        agent = MockAgent()
        result = AgentRunner.run_headless(agent)

        assert result.success is True
        assert agent.plan_called is True
        assert agent.execute_called is True

    @patch("persuade.agents.runner.Confirm.ask")
    def test_run_interactive_approval_denied(self, mock_confirm: MagicMock) -> None:
        """Test interactive run with user denying approval."""
        mock_confirm.return_value = False
        agent = MockAgent()

        async def run_test() -> AgentPhaseResult:
            runner = AgentRunner()
            return await runner.run_interactive(agent, auto_approve=False)

        result = asyncio.run(run_test())

        assert agent.plan_called is True
        assert agent.execute_called is False
        assert result.success is False
        assert result.error == "Aborted by user"

    @patch("persuade.agents.runner.Confirm.ask")
    def test_run_interactive_approval_granted(self, mock_confirm: MagicMock) -> None:
        """Test interactive run with user granting approval."""
        mock_confirm.return_value = True
        agent = MockAgent()

        async def run_test() -> AgentPhaseResult:
            runner = AgentRunner()
            return await runner.run_interactive(agent, auto_approve=False)

        result = asyncio.run(run_test())

        assert agent.plan_called is True
        assert agent.execute_called is True
        assert result.success is True


class TestCliMain:
    """Tests for cli_main helper function."""

    def test_cli_main_success(self) -> None:
        """Test cli_main returns 0 on success."""
        agent = MockAgent()
        exit_code = cli_main(agent, auto_approve=True)
        assert exit_code == 0

    def test_cli_main_failure(self) -> None:
        """Test cli_main returns 1 on failure."""
        agent = MockAgent(plan_success=False)
        exit_code = cli_main(agent, auto_approve=True)
        assert exit_code == 1


class TestIteratePRAgent:
    """Tests for IteratePRAgent."""

    def test_agent_initialization(self, tmp_path: Path) -> None:
        """Test agent initializes with correct paths."""
        from persuade.agents.iterate_pr import IteratePRAgent

        agent = IteratePRAgent(pr_number=67, project_dir=tmp_path)

        assert agent.pr_number == 67
        assert agent.project_dir == tmp_path
        assert agent.state_dir == tmp_path / ".claude" / "state"
        assert agent.state_file == tmp_path / ".claude" / "state" / "pr_67_monitor.json"

    def test_is_executable_returns_sdk_availability(self) -> None:
        """Test is_executable reflects SDK availability."""
        from persuade.agents.iterate_pr import AGENT_SDK_AVAILABLE, IteratePRAgent

        assert IteratePRAgent.is_executable() == AGENT_SDK_AVAILABLE

    def test_plan_no_state_file(self, tmp_path: Path) -> None:
        """Test plan phase when state file doesn't exist."""
        from persuade.agents.iterate_pr import IteratePRAgent, PRIterationPlan

        # Create scripts directory but no state file
        scripts_dir = tmp_path / ".claude" / "scripts"
        scripts_dir.mkdir(parents=True)

        agent = IteratePRAgent(pr_number=67, project_dir=tmp_path)
        result = asyncio.run(agent.plan())

        assert result.success is True
        # Without state file, iteration is not required
        # Check messages first (primary path)
        if result.messages:
            assert "No iteration needed" in result.messages[0]
        else:
            # Fallback: check plan summary if no messages
            plan = result.data.get("plan")
            assert isinstance(plan, PRIterationPlan)
            assert "No state file" in plan.summary

    def test_plan_with_state_file(self, tmp_path: Path) -> None:
        """Test plan phase with existing state file."""
        from persuade.agents.iterate_pr import IteratePRAgent

        # Create state directory and file
        state_dir = tmp_path / ".claude" / "state"
        state_dir.mkdir(parents=True)

        state_file = state_dir / "pr_67_monitor.json"
        state_data = {
            "pr_number": 67,
            "feedback_items": [
                {
                    "id": "1",
                    "item_type": "review_comment",
                    "author": "alice",
                    "author_type": "human",
                    "body": "Please fix this",
                    "url": "https://github.com/test/pr/67#comment-1",
                    "status": "unaddressed",
                }
            ],
        }
        state_file.write_text(json.dumps(state_data))

        agent = IteratePRAgent(pr_number=67, project_dir=tmp_path)
        result = asyncio.run(agent.plan())

        assert result.success is True
        plan = result.data.get("plan")
        assert plan is not None
        assert plan.pr_number == 67
        assert len(plan.feedback_items) == 1
        assert plan.feedback_items[0].author == "alice"

    def test_execute_without_sdk(self, tmp_path: Path) -> None:
        """Test execute phase without Claude Agent SDK installed."""
        from persuade.agents.iterate_pr import AGENT_SDK_AVAILABLE, IteratePRAgent

        if AGENT_SDK_AVAILABLE:
            pytest.skip("Claude Agent SDK is installed, skipping this test")

        agent = IteratePRAgent(pr_number=67, project_dir=tmp_path)
        plan_result = AgentPhaseResult(
            success=True,
            data={"plan": None},
        )

        result = asyncio.run(agent.execute(plan_result))

        assert result.success is False
        assert result.error is not None
        assert "Claude Agent SDK not installed" in result.error


class TestFeedbackItem:
    """Tests for FeedbackItem dataclass."""

    def test_feedback_item_creation(self) -> None:
        """Test creating a feedback item."""
        from persuade.agents.iterate_pr import FeedbackItem

        item = FeedbackItem(
            id="123",
            item_type="review_comment",
            author="bob",
            author_type="human",
            body="Please add tests",
            url="https://github.com/test/pr/1#comment-123",
            status="unaddressed",
            priority="MEDIUM",
            path="src/main.py",
            line=42,
        )

        assert item.id == "123"
        assert item.item_type == "review_comment"
        assert item.author == "bob"
        assert item.path == "src/main.py"
        assert item.line == 42

    def test_feedback_item_defaults(self) -> None:
        """Test feedback item with default values."""
        from persuade.agents.iterate_pr import FeedbackItem

        item = FeedbackItem(
            id="1",
            item_type="review",
            author="alice",
            author_type="human",
            body="LGTM",
            url="https://github.com/test/pr/1",
            status="addressed",
        )

        assert item.priority is None
        assert item.path is None
        assert item.line is None


class TestPRIterationPlan:
    """Tests for PRIterationPlan dataclass."""

    def test_plan_creation(self) -> None:
        """Test creating a PR iteration plan."""
        from persuade.agents.iterate_pr import FeedbackItem, PRIterationPlan

        item = FeedbackItem(
            id="1",
            item_type="review",
            author="alice",
            author_type="human",
            body="Fix this",
            url="https://github.com/test/pr/1",
            status="unaddressed",
        )

        plan = PRIterationPlan(
            pr_number=67,
            feedback_items=[item],
            ci_failures=["test-lint"],
            iteration_required=True,
            summary="1 feedback item",
        )

        assert plan.pr_number == 67
        assert len(plan.feedback_items) == 1
        assert len(plan.ci_failures) == 1
        assert plan.iteration_required is True

    def test_plan_defaults(self) -> None:
        """Test plan with default values."""
        from persuade.agents.iterate_pr import PRIterationPlan

        plan = PRIterationPlan(pr_number=67)

        assert plan.feedback_items == []
        assert plan.ci_failures == []
        assert plan.iteration_required is False
        assert plan.summary == ""
