import pytest
from unittest.mock import Mock, patch, MagicMock
from valenceai import ValenceClient
import base64


class TestStreamingAPI:

    @patch('valenceai.config.VALENCE_API_KEY', 'test_api_key')
    def test_client_has_streaming_property(self):
        """Test that ValenceClient has streaming property."""
        client = ValenceClient(api_key="test_key")
        assert hasattr(client, 'streaming')

    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_connect_returns_stream_connection(self):
        """Test that connect() returns a StreamConnection instance."""
        client = ValenceClient(api_key="test_key")
        stream = client.streaming.connect(model="4emotions")

        assert stream is not None
        assert hasattr(stream, 'connect')
        assert hasattr(stream, 'send_audio')
        assert hasattr(stream, 'disconnect')
        assert hasattr(stream, 'on_prediction')
        assert hasattr(stream, 'on_error')

    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_connect_with_custom_model(self):
        """Test creating connection with custom model."""
        client = ValenceClient(api_key="test_key")
        stream = client.streaming.connect(model="7emotions")

        assert stream.model == "7emotions"

    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_connect_uses_custom_websocket_url(self):
        """Test that streaming uses custom WebSocket URL when provided."""
        client = ValenceClient(api_key='test_key', websocket_url='wss://custom.example.com')
        stream = client.streaming.connect(model="4emotions")

        assert stream.websocket_url == 'wss://custom.example.com'

    @patch('valenceai.streaming.socketio.Client')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_stream_connection_connect(self, mock_socketio_client):
        """Test StreamConnection.connect() method."""
        # Mock socketio client
        mock_sio = Mock()
        mock_socketio_client.return_value = mock_sio

        client = ValenceClient(api_key="test_key")
        stream = client.streaming.connect(model="4emotions")

        # Connect
        stream.connect()

        # Verify socketio client was created and connected
        mock_socketio_client.assert_called_once()
        mock_sio.connect.assert_called_once()

    @patch('valenceai.streaming.socketio.Client')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_stream_connection_send_audio(self, mock_socketio_client):
        """Test StreamConnection.send_audio() method."""
        # Mock socketio client
        mock_sio = Mock()
        mock_socketio_client.return_value = mock_sio

        client = ValenceClient(api_key="test_key")
        stream = client.streaming.connect(model="4emotions")
        stream.connect()

        # Send audio data
        audio_data = b'fake_audio_bytes'
        stream.send_audio(audio_data)

        # Verify audio was encoded and emitted
        expected_b64 = base64.b64encode(audio_data).decode('utf-8')
        mock_sio.emit.assert_called()

    @patch('valenceai.streaming.socketio.Client')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_stream_connection_on_prediction_callback(self, mock_socketio_client):
        """Test StreamConnection.on_prediction() callback registration."""
        # Mock socketio client
        mock_sio = Mock()
        mock_socketio_client.return_value = mock_sio

        client = ValenceClient(api_key="test_key")
        stream = client.streaming.connect(model="4emotions")

        # Register callback
        callback = Mock()
        stream.on_prediction(callback)

        # Verify callback was stored
        assert stream._prediction_callback == callback

    @patch('valenceai.streaming.socketio.Client')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_stream_connection_on_error_callback(self, mock_socketio_client):
        """Test StreamConnection.on_error() callback registration."""
        # Mock socketio client
        mock_sio = Mock()
        mock_socketio_client.return_value = mock_sio

        client = ValenceClient(api_key="test_key")
        stream = client.streaming.connect(model="4emotions")

        # Register callback
        callback = Mock()
        stream.on_error(callback)

        # Verify callback was stored
        assert stream._error_callback == callback

    @patch('valenceai.streaming.socketio.Client')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_stream_connection_disconnect(self, mock_socketio_client):
        """Test StreamConnection.disconnect() method."""
        # Mock socketio client
        mock_sio = Mock()
        mock_socketio_client.return_value = mock_sio

        client = ValenceClient(api_key="test_key")
        stream = client.streaming.connect(model="4emotions")
        stream.connect()

        # Disconnect
        stream.disconnect()

        # Verify socketio client was disconnected
        mock_sio.disconnect.assert_called_once()

    @patch('valenceai.streaming.socketio.Client')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_prediction_callback_invoked(self, mock_socketio_client):
        """Test that prediction callback is invoked when prediction event fires."""
        # Track registered event handlers
        registered_handlers = {}

        def mock_on(event):
            def decorator(handler):
                registered_handlers[event] = handler
                return handler
            return decorator

        # Mock socketio client
        mock_sio = MagicMock()
        mock_sio.on = mock_on
        mock_socketio_client.return_value = mock_sio

        client = ValenceClient(api_key="test_key")
        stream = client.streaming.connect(model="4emotions")

        # Register prediction callback
        prediction_callback = Mock()
        stream.on_prediction(prediction_callback)

        stream.connect()

        # Simulate prediction event
        if 'prediction' in registered_handlers:
            test_prediction = {"main_emotion": "happy", "confidence": 0.9}
            registered_handlers['prediction'](test_prediction)

            # Verify callback was invoked
            prediction_callback.assert_called_once_with(test_prediction)

    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_connect_default_model(self):
        """Test that default model is 4emotions."""
        client = ValenceClient(api_key="test_key")
        stream = client.streaming.connect()

        assert stream.model == "4emotions"

    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_invalid_model_raises_error(self):
        """Test that invalid model raises ValueError."""
        client = ValenceClient(api_key="test_key")

        with pytest.raises(ValueError, match="Invalid model"):
            stream = client.streaming.connect(model="invalid_model")
