import pytest
import os
from unittest.mock import Mock, patch, mock_open
from valenceai import ValenceClient
from valenceai.exceptions import ValenceSDKException, AudioTooShortError, FileSizeLimitExceededError


class TestDiscreteAPI:

    def test_init_with_env_var(self):
        """Test initialization with API key from environment variable."""
        client = ValenceClient(api_key='test_api_key')
        assert client.api_key == 'test_api_key'
        assert client.headers == {"x-api-key": 'test_api_key'}
        assert hasattr(client, 'discrete')
        assert hasattr(client, 'asynch')
        assert hasattr(client, 'streaming')
        assert hasattr(client, 'rate_limit')

    def test_init_with_explicit_key(self):
        """Test initialization with explicitly provided API key."""
        client = ValenceClient(api_key='explicit_key')
        assert client.api_key == 'explicit_key'
        assert client.headers == {"x-api-key": 'explicit_key'}

    def test_init_with_custom_urls(self):
        """Test initialization with custom base URL and WebSocket URL."""
        client = ValenceClient(
            api_key='test_key',
            base_url='https://custom.example.com',
            websocket_url='wss://custom.example.com'
        )
        assert client.base_url == 'https://custom.example.com'
        assert client.websocket_url == 'wss://custom.example.com'

    def test_init_with_default_urls(self):
        """Test initialization uses production URLs by default."""
        client = ValenceClient(api_key='test_key')
        assert client.base_url == 'https://api.getvalenceai.com'
        assert client.websocket_url == 'wss://api.getvalenceai.com'

    @patch.dict(os.environ, {}, clear=True)
    @patch('valenceai.config.VALENCE_API_KEY', None)
    def test_init_without_api_key_raises_exception(self):
        """Test that initialization without API key raises exception."""
        with pytest.raises(ValenceSDKException, match="API key not provided"):
            ValenceClient()

    @patch('valenceai.client.os.path.getsize')
    @patch('valenceai.client.requests.post')
    @patch('builtins.open', new_callable=mock_open, read_data=b'fake_audio_data')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_file_success(self, mock_file, mock_post, mock_getsize):
        """Test successful emotion prediction with file upload."""
        # Mock file size (5MB - under the limit)
        mock_getsize.return_value = 5 * 1024 * 1024

        # Mock successful response
        mock_response = Mock()
        mock_response.json.return_value = {
            "emotions": {"happy": 0.8, "sad": 0.2},
            "dominant_emotion": "happy"
        }
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        client = ValenceClient(api_key="test_key")
        result = client.discrete.emotions(file_path="test_file.wav", model="7emotions")

        # Verify the request was made correctly
        mock_post.assert_called_once()
        call_args = mock_post.call_args

        # Check URL uses new v1 endpoint
        assert '/v1/discrete/emotion' in call_args[0][0]
        # Check headers
        assert call_args[1]['headers'] == {"x-api-key": 'test_key'}
        assert call_args[1]['params'] == {"model": "7emotions"}
        assert 'files' in call_args[1]

        # Check response
        assert result == {"emotions": {"happy": 0.8, "sad": 0.2}, "dominant_emotion": "happy"}

    @patch('valenceai.client.requests.post')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_json_payload_success(self, mock_post):
        """Test successful emotion prediction with JSON payload."""
        # Mock successful response
        mock_response = Mock()
        mock_response.json.return_value = {
            "emotions": {"happy": 0.9, "sad": 0.1},
            "dominant_emotion": "happy"
        }
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        client = ValenceClient(api_key="test_key")
        audio_array = [0.1, 0.2, 0.3, 0.4]
        result = client.discrete.emotions(audio_array=audio_array, model="4emotions")

        # Verify the request was made correctly
        mock_post.assert_called_once()
        call_args = mock_post.call_args

        # Check URL uses new v1 endpoint
        assert '/v1/discrete/emotion' in call_args[0][0]
        # Check JSON payload
        assert call_args[1]['json'] == {"payload": [audio_array]}
        assert call_args[1]['params'] == {"model": "4emotions"}

        # Check response
        assert result == {"emotions": {"happy": 0.9, "sad": 0.1}, "dominant_emotion": "happy"}

    @patch('valenceai.client.os.path.getsize')
    @patch('valenceai.client.requests.post')
    @patch('builtins.open', new_callable=mock_open, read_data=b'fake_audio_data')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_default_model(self, mock_file, mock_post, mock_getsize):
        """Test emotion prediction with default model."""
        mock_getsize.return_value = 5 * 1024 * 1024
        mock_response = Mock()
        mock_response.json.return_value = {"result": "success"}
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        client = ValenceClient(api_key="test_key")
        client.discrete.emotions(file_path="test_file.wav")

        # Check that default model is used
        call_args = mock_post.call_args
        assert call_args[1]['params'] == {"model": "4emotions"}

    @patch('valenceai.client.os.path.getsize')
    @patch('valenceai.client.requests.post')
    @patch('builtins.open', new_callable=mock_open, read_data=b'fake_audio_data')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_http_error(self, mock_file, mock_post, mock_getsize):
        """Test emotion prediction with HTTP error."""
        mock_getsize.return_value = 5 * 1024 * 1024
        mock_response = Mock()
        mock_response.raise_for_status.side_effect = Exception("HTTP 500 Error")
        mock_post.return_value = mock_response

        client = ValenceClient(api_key="test_key")

        with pytest.raises(Exception, match="HTTP 500 Error"):
            client.discrete.emotions(file_path="test_file.wav")

    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_file_not_found(self):
        """Test emotion prediction with non-existent file."""
        client = ValenceClient(api_key="test_key")

        with pytest.raises(FileNotFoundError):
            client.discrete.emotions(file_path="non_existent_file.wav")

    @patch('valenceai.client.os.path.getsize')
    @patch('valenceai.client.requests.post')
    @patch('builtins.open', new_callable=mock_open, read_data=b'fake_audio_data')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_custom_model(self, mock_file, mock_post, mock_getsize):
        """Test emotion prediction with custom model parameter."""
        mock_getsize.return_value = 5 * 1024 * 1024
        mock_response = Mock()
        mock_response.json.return_value = {"result": "success"}
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        client = ValenceClient(api_key="test_key")
        client.discrete.emotions(file_path="test_file.wav", model="7emotions")

        # Verify custom model parameter
        call_args = mock_post.call_args
        assert call_args[1]['params'] == {"model": "7emotions"}

    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_both_file_and_array_raises_error(self):
        """Test that providing both file_path and audio_array raises an error."""
        client = ValenceClient(api_key="test_key")

        with pytest.raises(ValueError, match="Provide either file_path or audio_array"):
            client.discrete.emotions(file_path="test.wav", audio_array=[0.1, 0.2])

    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_neither_file_nor_array_raises_error(self):
        """Test that providing neither file_path nor audio_array raises an error."""
        client = ValenceClient(api_key="test_key")

        with pytest.raises(ValueError, match="Must provide either file_path or audio_array"):
            client.discrete.emotions()

    @patch('valenceai.client.os.path.getsize')
    @patch('valenceai.client.requests.post')
    @patch('builtins.open', new_callable=mock_open, read_data=b'fake_audio_data')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_audio_too_short_error(self, mock_file, mock_post, mock_getsize):
        """Test that AudioTooShortError is raised when audio is too short."""
        mock_getsize.return_value = 5 * 1024 * 1024
        mock_response = Mock()
        mock_response.status_code = 400
        mock_response.json.return_value = {
            "error": "Audio file is too short. Minimum duration: 4.5 seconds, provided: 1.50 seconds",
            "error_code": "AUDIO_TOO_SHORT",
            "min_duration_seconds": 4.5,
            "actual_duration_seconds": 1.5
        }
        mock_post.return_value = mock_response

        client = ValenceClient(api_key="test_key")

        with pytest.raises(AudioTooShortError) as exc_info:
            client.discrete.emotions(file_path="short_audio.wav")

        assert exc_info.value.min_duration == 4.5
        assert exc_info.value.actual_duration == 1.5
        assert "too short" in str(exc_info.value)

    @patch('valenceai.client.requests.post')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_audio_too_short_error_json_payload(self, mock_post):
        """Test that AudioTooShortError is raised for JSON payload when audio is too short."""
        mock_response = Mock()
        mock_response.status_code = 400
        mock_response.json.return_value = {
            "error": "Audio file is too short. Minimum duration: 4.5 seconds, provided: 2.00 seconds",
            "error_code": "AUDIO_TOO_SHORT",
            "min_duration_seconds": 4.5,
            "actual_duration_seconds": 2.0
        }
        mock_post.return_value = mock_response

        client = ValenceClient(api_key="test_key")

        with pytest.raises(AudioTooShortError) as exc_info:
            client.discrete.emotions(audio_array=[0.1, 0.2, 0.3])

        assert exc_info.value.min_duration == 4.5
        assert exc_info.value.actual_duration == 2.0

    @patch('valenceai.client.os.path.getsize')
    @patch('builtins.open', new_callable=mock_open, read_data=b'fake_audio_data')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_file_size_exceeds_6mb(self, mock_file, mock_getsize):
        """Test that FileSizeLimitExceededError is raised when file exceeds 6MB."""
        # Mock file size to be 7MB (7 * 1024 * 1024 bytes)
        mock_getsize.return_value = 7 * 1024 * 1024

        client = ValenceClient(api_key="test_key")

        with pytest.raises(FileSizeLimitExceededError) as exc_info:
            client.discrete.emotions(file_path="large_audio.wav")

        assert exc_info.value.max_size_mb == 6.0
        assert exc_info.value.actual_size_mb == 7.0
        assert "exceeds discrete API maximum" in str(exc_info.value)

    @patch('valenceai.client.os.path.getsize')
    @patch('builtins.open', new_callable=mock_open, read_data=b'fake_audio_data')
    @patch('valenceai.client.requests.post')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_discrete_emotions_file_size_under_6mb_succeeds(self, mock_post, mock_file, mock_getsize):
        """Test that file upload succeeds when file is under 6MB."""
        # Mock file size to be 5MB (5 * 1024 * 1024 bytes)
        mock_getsize.return_value = 5 * 1024 * 1024

        mock_response = Mock()
        mock_response.json.return_value = {
            "emotions": {"happy": 0.8, "sad": 0.2},
            "dominant_emotion": "happy"
        }
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        client = ValenceClient(api_key="test_key")
        result = client.discrete.emotions(file_path="audio.wav")

        # Should succeed and make the API call
        mock_post.assert_called_once()
        assert result == {"emotions": {"happy": 0.8, "sad": 0.2}, "dominant_emotion": "happy"}