"""
Tests for ConversationCallbackProcessor and ConversationCallback models.
"""

import json

import pytest
from storage.conversation_callback import (
    CallbackStatus,
    ConversationCallback,
    ConversationCallbackProcessor,
)
from storage.stored_conversation_metadata import StoredConversationMetadata

from openhands.events.observation.agent import AgentStateChangedObservation


class MockConversationCallbackProcessor(ConversationCallbackProcessor):
    """Mock implementation of ConversationCallbackProcessor for testing."""

    name: str = 'test'
    config: dict = {}

    def __init__(self, name: str = 'test', config: dict | None = None, **kwargs):
        super().__init__(name=name, config=config or {}, **kwargs)
        self.call_count = 0
        self.last_conversation_id: str | None = None

    def __call__(
        self, callback: ConversationCallback, observation: AgentStateChangedObservation
    ) -> None:
        """Mock implementation that tracks calls."""
        self.call_count += 1
        self.last_conversation_id = callback.conversation_id


class TestConversationCallbackProcessor:
    """Test the ConversationCallbackProcessor abstract base class."""

    def test_mock_processor_creation(self):
        """Test that we can create a mock processor."""
        processor = MockConversationCallbackProcessor(
            name='test_processor', config={'key': 'value'}
        )
        assert processor.name == 'test_processor'
        assert processor.config == {'key': 'value'}
        assert processor.call_count == 0
        assert processor.last_conversation_id is None

    def test_mock_processor_call(self):
        """Test that the mock processor can be called."""
        callback = ConversationCallback(conversation_id='test_conversation_id')
        processor = MockConversationCallbackProcessor()
        processor(
            callback,
            AgentStateChangedObservation('foobar', 'awaiting_user_input'),
        )

        assert processor.call_count == 1
        assert processor.last_conversation_id == 'test_conversation_id'

    def test_processor_serialization(self):
        """Test that processors can be serialized to JSON."""
        processor = MockConversationCallbackProcessor(
            name='test', config={'setting': 'value'}
        )
        json_data = processor.model_dump_json()

        # Should be able to parse the JSON
        data = json.loads(json_data)
        assert data['name'] == 'test'
        assert data['config'] == {'setting': 'value'}


class TestConversationCallback:
    """Test the ConversationCallback SQLAlchemy model."""

    @pytest.fixture
    def conversation_metadata(self, session_maker):
        """Create a test conversation metadata record."""
        with session_maker() as session:
            metadata = StoredConversationMetadata(
                conversation_id='test_conversation_123', user_id='test_user_456'
            )
            session.add(metadata)
            session.commit()
            session.refresh(metadata)
            yield metadata

            # Cleanup
            session.delete(metadata)
            session.commit()

    def test_callback_creation(self, conversation_metadata, session_maker):
        """Test creating a conversation callback."""
        processor = MockConversationCallbackProcessor(name='test_processor')

        with session_maker() as session:
            callback = ConversationCallback(
                conversation_id=conversation_metadata.conversation_id,
                status=CallbackStatus.ACTIVE,
                processor_type='tests.unit.test_conversation_processor.MockConversationCallbackProcessor',
                processor_json=processor.model_dump_json(),
            )
            session.add(callback)
            session.commit()
            session.refresh(callback)

            assert callback.id is not None
            assert callback.conversation_id == conversation_metadata.conversation_id
            assert callback.status == CallbackStatus.ACTIVE
            assert callback.created_at is not None
            assert callback.updated_at is not None

            # Cleanup
            session.delete(callback)
            session.commit()

    def test_set_processor(self, conversation_metadata, session_maker):
        """Test setting a processor on a callback."""
        processor = MockConversationCallbackProcessor(
            name='test_processor', config={'key': 'value'}
        )

        callback = ConversationCallback(
            conversation_id=conversation_metadata.conversation_id
        )
        callback.set_processor(processor)

        assert (
            callback.processor_type
            == 'enterprise.tests.unit.test_conversation_callback_processor.MockConversationCallbackProcessor'
        )

        # Verify the JSON contains the processor data
        processor_data = json.loads(callback.processor_json)
        assert processor_data['name'] == 'test_processor'
        assert processor_data['config'] == {'key': 'value'}

    def test_get_processor(self, conversation_metadata, session_maker):
        """Test getting a processor from a callback."""
        processor = MockConversationCallbackProcessor(
            name='test_processor', config={'key': 'value'}
        )

        callback = ConversationCallback(
            conversation_id=conversation_metadata.conversation_id
        )
        callback.set_processor(processor)

        # Get the processor back
        retrieved_processor = callback.get_processor()

        assert isinstance(retrieved_processor, MockConversationCallbackProcessor)
        assert retrieved_processor.name == 'test_processor'
        assert retrieved_processor.config == {'key': 'value'}

    def test_callback_status_enum(self):
        """Test the CallbackStatus enum."""
        assert CallbackStatus.ACTIVE.value == 'ACTIVE'
        assert CallbackStatus.COMPLETED.value == 'COMPLETED'
        assert CallbackStatus.ERROR.value == 'ERROR'

    def test_callback_foreign_key_constraint(
        self, conversation_metadata, session_maker
    ):
        """Test that the foreign key constraint works."""
        with session_maker() as session:
            # This should work with valid conversation_id
            callback = ConversationCallback(
                conversation_id=conversation_metadata.conversation_id,
                processor_type='test.Processor',
                processor_json='{}',
            )
            session.add(callback)
            session.commit()

            # Cleanup
            session.delete(callback)
            session.commit()

            # Note: SQLite doesn't enforce foreign key constraints by default in tests
            # In a real PostgreSQL database, this would raise an integrity error
            # For now, we just test that the callback can be created with valid data
