"""
Tests for the GitlabCallbackProcessor.
"""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from integrations.gitlab.gitlab_view import GitlabIssueComment
from integrations.types import UserData
from server.conversation_callback_processor.gitlab_callback_processor import (
    GitlabCallbackProcessor,
)
from storage.conversation_callback import CallbackStatus, ConversationCallback

from openhands.core.schema.agent import AgentState
from openhands.events.observation.agent import AgentStateChangedObservation


@pytest.fixture
def mock_gitlab_view():
    """Create a mock GitlabViewType for testing."""
    # Use a simple dict that matches GitlabIssue structure
    return GitlabIssueComment(
        installation_id='test_installation',
        issue_number=789,
        project_id=456,
        full_repo_name='test/repo',
        is_public_repo=True,
        user_info=UserData(
            user_id=123, username='test_user', keycloak_user_id='test_keycloak_id'
        ),
        raw_payload={'source': 'gitlab', 'message': {'test': 'data'}},
        conversation_id='test_conversation',
        should_extract=True,
        send_summary_instruction=True,
        title='',
        description='',
        previous_comments=[],
        is_mr=False,
        comment_body='sdfs',
        discussion_id='test_discussion',
        confidential=False,
    )


@pytest.fixture
def gitlab_callback_processor(mock_gitlab_view):
    """Create a GitlabCallbackProcessor instance for testing."""
    return GitlabCallbackProcessor(
        gitlab_view=mock_gitlab_view,
        send_summary_instruction=True,
    )


class TestGitlabCallbackProcessor:
    """Test the GitlabCallbackProcessor class."""

    def test_model_validation(self, mock_gitlab_view):
        """Test the model validation of GitlabCallbackProcessor."""
        # Test with all required fields
        processor = GitlabCallbackProcessor(
            gitlab_view=mock_gitlab_view,
        )
        # Check that gitlab_view was converted to a GitlabIssue object
        assert hasattr(processor.gitlab_view, 'issue_number')
        assert processor.gitlab_view.issue_number == 789
        assert processor.gitlab_view.full_repo_name == 'test/repo'
        assert processor.send_summary_instruction is True

        # Test with custom send_summary_instruction
        processor = GitlabCallbackProcessor(
            gitlab_view=mock_gitlab_view,
            send_summary_instruction=False,
        )
        assert hasattr(processor.gitlab_view, 'issue_number')
        assert processor.gitlab_view.issue_number == 789
        assert processor.send_summary_instruction is False

    def test_serialization(self, mock_gitlab_view):
        """Test serialization and deserialization of GitlabCallbackProcessor."""
        original_processor = GitlabCallbackProcessor(
            gitlab_view=mock_gitlab_view,
            send_summary_instruction=True,
        )

        # Serialize to JSON
        json_data = original_processor.model_dump_json()
        assert isinstance(json_data, str)

        # Deserialize from JSON
        deserialized_processor = GitlabCallbackProcessor.model_validate_json(json_data)
        assert (
            deserialized_processor.send_summary_instruction
            == original_processor.send_summary_instruction
        )
        assert (
            deserialized_processor.gitlab_view.issue_number
            == original_processor.gitlab_view.issue_number
        )

        assert isinstance(
            deserialized_processor.gitlab_view.issue_number,
            type(original_processor.gitlab_view.issue_number),
        )
        # Note: gitlab_view will be serialized as a dict, so we can't directly compare objects

    @pytest.mark.asyncio
    @patch(
        'server.conversation_callback_processor.gitlab_callback_processor.get_summary_instruction'
    )
    @patch(
        'server.conversation_callback_processor.gitlab_callback_processor.conversation_manager'
    )
    @patch(
        'server.conversation_callback_processor.gitlab_callback_processor.session_maker'
    )
    async def test_call_with_send_summary_instruction(
        self,
        mock_session_maker,
        mock_conversation_manager,
        mock_get_summary_instruction,
        gitlab_callback_processor,
    ):
        """Test the __call__ method when send_summary_instruction is True."""
        # Setup mocks
        mock_session = MagicMock()
        mock_session_maker.return_value.__enter__.return_value = mock_session
        mock_conversation_manager.send_event_to_conversation = AsyncMock()
        mock_get_summary_instruction.return_value = (
            "I'm a man of few words. Any questions?"
        )

        # Create a callback and observation
        callback = ConversationCallback(
            conversation_id='conv123',
            status=CallbackStatus.ACTIVE,
            processor_type=f'{GitlabCallbackProcessor.__module__}.{GitlabCallbackProcessor.__name__}',
            processor_json=gitlab_callback_processor.model_dump_json(),
        )
        observation = AgentStateChangedObservation(
            content='', agent_state=AgentState.AWAITING_USER_INPUT
        )

        # Call the processor
        await gitlab_callback_processor(callback, observation)

        # Verify that send_event_to_conversation was called
        mock_conversation_manager.send_event_to_conversation.assert_called_once()

        # Verify that the processor state was updated
        assert gitlab_callback_processor.send_summary_instruction is False
        mock_session.merge.assert_called_once_with(callback)
        mock_session.commit.assert_called_once()

    @pytest.mark.asyncio
    @patch(
        'server.conversation_callback_processor.gitlab_callback_processor.conversation_manager'
    )
    @patch(
        'server.conversation_callback_processor.gitlab_callback_processor.extract_summary_from_conversation_manager'
    )
    @patch(
        'server.conversation_callback_processor.gitlab_callback_processor.asyncio.create_task'
    )
    @patch(
        'server.conversation_callback_processor.gitlab_callback_processor.session_maker'
    )
    async def test_call_with_extract_summary(
        self,
        mock_session_maker,
        mock_create_task,
        mock_extract_summary,
        mock_conversation_manager,
        gitlab_callback_processor,
    ):
        """Test the __call__ method when send_summary_instruction is False."""
        # Setup mocks
        mock_session = MagicMock()
        mock_session_maker.return_value.__enter__.return_value = mock_session
        mock_extract_summary.return_value = 'Test summary'
        # Ensure we don't leak an un-awaited coroutine when create_task is mocked
        mock_create_task.side_effect = lambda coro: (coro.close(), None)[1]

        # Set send_summary_instruction to False
        gitlab_callback_processor.send_summary_instruction = False

        # Create a callback and observation
        callback = ConversationCallback(
            conversation_id='conv123',
            status=CallbackStatus.ACTIVE,
            processor_type=f'{GitlabCallbackProcessor.__module__}.{GitlabCallbackProcessor.__name__}',
            processor_json=gitlab_callback_processor.model_dump_json(),
        )
        observation = AgentStateChangedObservation(
            content='', agent_state=AgentState.FINISHED
        )

        # Call the processor
        await gitlab_callback_processor(callback, observation)

        # Verify that extract_summary_from_conversation_manager was called
        mock_extract_summary.assert_called_once_with(
            mock_conversation_manager, 'conv123'
        )

        # Verify that create_task was called to send the message
        mock_create_task.assert_called_once()

        # Verify that the callback status was updated
        assert callback.status == CallbackStatus.COMPLETED
        mock_session.merge.assert_called_once_with(callback)
        mock_session.commit.assert_called_once()

    @pytest.mark.asyncio
    async def test_call_with_non_terminal_state(self, gitlab_callback_processor):
        """Test the __call__ method with a non-terminal agent state."""
        # Create a callback and observation with a non-terminal state
        callback = ConversationCallback(
            conversation_id='conv123',
            status=CallbackStatus.ACTIVE,
            processor_type=f'{GitlabCallbackProcessor.__module__}.{GitlabCallbackProcessor.__name__}',
            processor_json=gitlab_callback_processor.model_dump_json(),
        )
        observation = AgentStateChangedObservation(
            content='', agent_state=AgentState.RUNNING
        )

        # Call the processor
        await gitlab_callback_processor(callback, observation)

        # Verify that nothing happened (early return)
        assert gitlab_callback_processor.send_summary_instruction is True
