"""Tests for issue parser agent."""

from unittest.mock import Mock, patch

import pytest
from anthropic import Anthropic
from anthropic.types import Message, TextBlock, Usage

from src.parser import clear_cache, parse_issue_metadata


@pytest.fixture(autouse=True)
def setup_test_env(monkeypatch: pytest.MonkeyPatch) -> None:
    """Set up test environment variables."""
    monkeypatch.setenv("GITEA_WEBHOOK_SECRET", "test-secret")
    monkeypatch.setenv("GITEA_URL", "https://test.example.com")
    monkeypatch.setenv("ANTHROPIC_API_KEY", "test-anthropic-key")
    monkeypatch.setenv("LOG_LEVEL", "debug")


@pytest.fixture
def sample_complete_issue_body() -> str:
    """Complete issue body with all fields."""
    return """## Objective

Create AI agent (Sonnet) that parses issue markdown body to extract structured metadata.

## Implementation Details

1. Create parse_issue_metadata() function
2. Use Anthropic API with Sonnet model

## Context Estimate

• Files to modify: 3 (parser.py, agent.py, models.py)
• Implementation complexity: medium (20000 tokens)
• Test requirements: medium (10000 tokens)
• Documentation: medium (3000 tokens)
• **Total estimated: 46800 tokens**
• **Recommended agent: sonnet**

## Difficulty

medium

## Dependencies

• Blocked by: #157 (COORD-001 - needs webhook to trigger parser)
• Blocks: #159 (COORD-003 - queue needs parsed metadata)

## Acceptance Criteria

[ ] Parser extracts all required fields
[ ] Returns valid JSON matching schema
"""


@pytest.fixture
def sample_minimal_issue_body() -> str:
    """Minimal issue body with only required fields."""
    return """## Objective

Fix the login bug.

## Acceptance Criteria

[ ] Bug is fixed
"""


@pytest.fixture
def sample_malformed_issue_body() -> str:
    """Malformed issue body to test graceful failure."""
    return """This is just random text without proper sections.

Some more random content here.
"""


@pytest.fixture
def mock_anthropic_response() -> Message:
    """Mock Anthropic API response."""
    return Message(
        id="msg_123",
        type="message",
        role="assistant",
        content=[
            TextBlock(
                type="text",
                text=(
                    '{"estimated_context": 46800, "difficulty": "medium", '
                    '"assigned_agent": "sonnet", "blocks": [159], "blocked_by": [157]}'
                ),
            )
        ],
        model="claude-sonnet-4.5-20250929",
        stop_reason="end_turn",
        usage=Usage(input_tokens=500, output_tokens=50)
    )


@pytest.fixture
def mock_anthropic_minimal_response() -> Message:
    """Mock Anthropic API response for minimal issue."""
    return Message(
        id="msg_124",
        type="message",
        role="assistant",
        content=[
            TextBlock(
                type="text",
                text=(
                    '{"estimated_context": 50000, "difficulty": "medium", '
                    '"assigned_agent": "sonnet", "blocks": [], "blocked_by": []}'
                ),
            )
        ],
        model="claude-sonnet-4.5-20250929",
        stop_reason="end_turn",
        usage=Usage(input_tokens=200, output_tokens=40)
    )


@pytest.fixture(autouse=True)
def reset_cache() -> None:
    """Clear cache before each test."""
    clear_cache()


class TestParseIssueMetadata:
    """Tests for parse_issue_metadata function."""

    @patch("src.parser.Anthropic")
    def test_parse_complete_issue(
        self,
        mock_anthropic_class: Mock,
        sample_complete_issue_body: str,
        mock_anthropic_response: Message
    ) -> None:
        """Test parsing complete issue body with all fields."""
        # Setup mock
        mock_client = Mock(spec=Anthropic)
        mock_messages = Mock()
        mock_messages.create = Mock(return_value=mock_anthropic_response)
        mock_client.messages = mock_messages
        mock_anthropic_class.return_value = mock_client

        # Parse issue
        result = parse_issue_metadata(sample_complete_issue_body, 158)

        # Verify result
        assert result.estimated_context == 46800
        assert result.difficulty == "medium"
        assert result.assigned_agent == "sonnet"
        assert result.blocks == [159]
        assert result.blocked_by == [157]

        # Verify API was called correctly
        mock_messages.create.assert_called_once()
        call_args = mock_messages.create.call_args
        assert call_args.kwargs["model"] == "claude-sonnet-4.5-20250929"
        assert call_args.kwargs["max_tokens"] == 1024
        assert call_args.kwargs["temperature"] == 0

    @patch("src.parser.Anthropic")
    def test_parse_minimal_issue(
        self,
        mock_anthropic_class: Mock,
        sample_minimal_issue_body: str,
        mock_anthropic_minimal_response: Message
    ) -> None:
        """Test parsing minimal issue body uses defaults."""
        # Setup mock
        mock_client = Mock(spec=Anthropic)
        mock_messages = Mock()
        mock_messages.create = Mock(return_value=mock_anthropic_minimal_response)
        mock_client.messages = mock_messages
        mock_anthropic_class.return_value = mock_client

        # Parse issue
        result = parse_issue_metadata(sample_minimal_issue_body, 999)

        # Verify defaults are used
        assert result.estimated_context == 50000
        assert result.difficulty == "medium"
        assert result.assigned_agent == "sonnet"
        assert result.blocks == []
        assert result.blocked_by == []

    @patch("src.parser.Anthropic")
    def test_parse_malformed_issue_returns_defaults(
        self,
        mock_anthropic_class: Mock,
        sample_malformed_issue_body: str
    ) -> None:
        """Test malformed issue body returns graceful defaults."""
        # Setup mock to return invalid JSON
        mock_client = Mock(spec=Anthropic)
        mock_messages = Mock()
        mock_messages.create = Mock(
            return_value=Message(
                id="msg_125",
                type="message",
                role="assistant",
                content=[TextBlock(type="text", text='{"invalid": "json"')],
                model="claude-sonnet-4.5-20250929",
                stop_reason="end_turn",
                usage=Usage(input_tokens=100, output_tokens=20)
            )
        )
        mock_client.messages = mock_messages
        mock_anthropic_class.return_value = mock_client

        # Parse issue
        result = parse_issue_metadata(sample_malformed_issue_body, 888)

        # Verify defaults
        assert result.estimated_context == 50000
        assert result.difficulty == "medium"
        assert result.assigned_agent == "sonnet"
        assert result.blocks == []
        assert result.blocked_by == []

    @patch("src.parser.Anthropic")
    def test_api_failure_returns_defaults(
        self,
        mock_anthropic_class: Mock,
        sample_complete_issue_body: str
    ) -> None:
        """Test API failure returns defaults with error logged."""
        # Setup mock to raise exception
        mock_client = Mock(spec=Anthropic)
        mock_messages = Mock()
        mock_messages.create = Mock(side_effect=Exception("API Error"))
        mock_client.messages = mock_messages
        mock_anthropic_class.return_value = mock_client

        # Parse issue
        result = parse_issue_metadata(sample_complete_issue_body, 777)

        # Verify defaults
        assert result.estimated_context == 50000
        assert result.difficulty == "medium"
        assert result.assigned_agent == "sonnet"
        assert result.blocks == []
        assert result.blocked_by == []

    @patch("src.parser.Anthropic")
    def test_caching_avoids_duplicate_api_calls(
        self,
        mock_anthropic_class: Mock,
        sample_complete_issue_body: str,
        mock_anthropic_response: Message
    ) -> None:
        """Test that caching prevents duplicate API calls for same issue."""
        # Setup mock
        mock_client = Mock(spec=Anthropic)
        mock_messages = Mock()
        mock_messages.create = Mock(return_value=mock_anthropic_response)
        mock_client.messages = mock_messages
        mock_anthropic_class.return_value = mock_client

        # Parse same issue twice
        result1 = parse_issue_metadata(sample_complete_issue_body, 158)
        result2 = parse_issue_metadata(sample_complete_issue_body, 158)

        # Verify API was called only once
        assert mock_messages.create.call_count == 1

        # Verify both results are identical
        assert result1.model_dump() == result2.model_dump()

    @patch("src.parser.Anthropic")
    def test_different_issues_not_cached(
        self,
        mock_anthropic_class: Mock,
        sample_complete_issue_body: str,
        sample_minimal_issue_body: str,
        mock_anthropic_response: Message
    ) -> None:
        """Test that different issues result in separate API calls."""
        # Setup mock
        mock_client = Mock(spec=Anthropic)
        mock_messages = Mock()
        mock_messages.create = Mock(return_value=mock_anthropic_response)
        mock_client.messages = mock_messages
        mock_anthropic_class.return_value = mock_client

        # Parse different issues
        parse_issue_metadata(sample_complete_issue_body, 158)
        parse_issue_metadata(sample_minimal_issue_body, 159)

        # Verify API was called twice
        assert mock_messages.create.call_count == 2

    @patch("src.parser.Anthropic")
    def test_difficulty_validation(
        self,
        mock_anthropic_class: Mock,
        sample_complete_issue_body: str
    ) -> None:
        """Test that difficulty values are validated."""
        # Setup mock with invalid difficulty
        mock_client = Mock(spec=Anthropic)
        mock_messages = Mock()
        mock_messages.create = Mock(
            return_value=Message(
                id="msg_126",
                type="message",
                role="assistant",
                content=[
                    TextBlock(
                        type="text",
                        text=(
                            '{"estimated_context": 10000, "difficulty": "invalid", '
                            '"assigned_agent": "sonnet", "blocks": [], "blocked_by": []}'
                        ),
                    )
                ],
                model="claude-sonnet-4.5-20250929",
                stop_reason="end_turn",
                usage=Usage(input_tokens=100, output_tokens=20)
            )
        )
        mock_client.messages = mock_messages
        mock_anthropic_class.return_value = mock_client

        # Parse issue
        result = parse_issue_metadata(sample_complete_issue_body, 666)

        # Should default to "medium" for invalid difficulty
        assert result.difficulty == "medium"

    @patch("src.parser.Anthropic")
    def test_agent_validation(
        self,
        mock_anthropic_class: Mock,
        sample_complete_issue_body: str
    ) -> None:
        """Test that agent values are validated."""
        # Setup mock with invalid agent
        mock_client = Mock(spec=Anthropic)
        mock_messages = Mock()
        mock_messages.create = Mock(
            return_value=Message(
                id="msg_127",
                type="message",
                role="assistant",
                content=[
                    TextBlock(
                        type="text",
                        text=(
                            '{"estimated_context": 10000, "difficulty": "medium", '
                            '"assigned_agent": "invalid_agent", "blocks": [], "blocked_by": []}'
                        ),
                    )
                ],
                model="claude-sonnet-4.5-20250929",
                stop_reason="end_turn",
                usage=Usage(input_tokens=100, output_tokens=20)
            )
        )
        mock_client.messages = mock_messages
        mock_anthropic_class.return_value = mock_client

        # Parse issue
        result = parse_issue_metadata(sample_complete_issue_body, 555)

        # Should default to "sonnet" for invalid agent
        assert result.assigned_agent == "sonnet"

    @patch("src.parser.Anthropic")
    def test_parse_time_performance(
        self,
        mock_anthropic_class: Mock,
        sample_complete_issue_body: str,
        mock_anthropic_response: Message
    ) -> None:
        """Test that parsing completes within performance target."""
        import time

        # Setup mock
        mock_client = Mock(spec=Anthropic)
        mock_messages = Mock()
        mock_messages.create = Mock(return_value=mock_anthropic_response)
        mock_client.messages = mock_messages
        mock_anthropic_class.return_value = mock_client

        # Measure parse time
        start_time = time.time()
        parse_issue_metadata(sample_complete_issue_body, 158)
        elapsed_time = time.time() - start_time

        # Should complete within 2 seconds (mocked, so should be instant)
        assert elapsed_time < 2.0
