import pytest
import os
import time
from unittest.mock import Mock, patch, mock_open, MagicMock
from collections import OrderedDict
from valenceai import ValenceClient
from valenceai.exceptions import ValenceSDKException, UploadError, PredictionError, AudioTooShortError, AudioTooLongError


class TestAsyncAPI:

    @patch('valenceai.config.VALENCE_API_KEY', 'test_api_key')
    def test_init_default_parameters(self):
        """Test initialization with default parameters."""
        client = ValenceClient(api_key="test_key")
        assert client.api_key == 'test_key'
        assert client.asynch.part_size == 5 * 1024 * 1024
        assert client.asynch.show_progress == True
        assert client.asynch.max_threads == 3

    @patch('valenceai.config.VALENCE_API_KEY', 'test_api_key')
    def test_init_custom_parameters(self):
        """Test initialization with custom parameters."""
        client = ValenceClient(api_key="test_key", part_size=1024, show_progress=False, max_threads=5)
        assert client.asynch.part_size == 1024
        assert client.asynch.show_progress == False
        assert client.asynch.max_threads == 5
    
    @patch('valenceai.client.requests.get')
    @patch('valenceai.client.requests.post')
    @patch('valenceai.client.ThreadPoolExecutor')
    @patch('valenceai.client.os.path.getsize')
    @patch('builtins.open', new_callable=mock_open, read_data=b'fake_audio_data' * 1000)
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_upload_success(self, mock_file, mock_getsize, mock_executor, mock_post, mock_get):
        """Test successful file upload."""
        # Mock file size
        mock_getsize.return_value = 10 * 1024 * 1024  # 10MB
        
        # Mock initiate response
        mock_init_response = Mock()
        mock_init_response.json.return_value = {
            "upload_id": "test_upload_id",
            "request_id": "test_request_id",
            "presigned_urls": [
                {"part_number": 1, "url": "https://s3.aws.com/part1"},
                {"part_number": 2, "url": "https://s3.aws.com/part2"}
            ]
        }
        mock_init_response.raise_for_status.return_value = None
        mock_get.return_value = mock_init_response
        
        # Mock upload part responses
        mock_part_response = Mock()
        mock_part_response.headers = {"ETag": "test_etag"}
        mock_part_response.raise_for_status.return_value = None
        
        # Mock ThreadPoolExecutor
        mock_executor_instance = MagicMock()
        mock_executor.__enter__.return_value = mock_executor_instance
        
        # Mock futures
        mock_future1 = Mock()
        mock_future1.result.return_value = {"ETag": "etag1", "PartNumber": 1}
        mock_future2 = Mock()
        mock_future2.result.return_value = {"ETag": "etag2", "PartNumber": 2}
        
        mock_executor_instance.submit.side_effect = [mock_future1, mock_future2]
        
        # Mock as_completed
        with patch('valenceai.client.as_completed', return_value=[mock_future1, mock_future2]):
            # Mock complete response
            mock_complete_response = Mock()
            mock_complete_response.raise_for_status.return_value = None
            mock_post.return_value = mock_complete_response

            client = ValenceClient(api_key="test_key", show_progress=False)
            
            # Mock upload_part method
            client.asynch._upload_part = Mock(side_effect=[
                {"ETag": "etag1", "PartNumber": 1},
                {"ETag": "etag2", "PartNumber": 2}
            ])
            
            result = client.asynch.upload("test_file.wav")
            
            assert result == "test_request_id"
            
            # Verify initiate call
            mock_get.assert_called_once()
            
            # Verify complete call
            mock_post.assert_called_once()
    
    @patch('valenceai.client.requests.put')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_upload_part_success(self, mock_put):
        """Test successful part upload."""
        mock_response = Mock()
        mock_response.headers = {"ETag": "test_etag_123"}
        mock_response.raise_for_status.return_value = None
        mock_put.return_value = mock_response

        client = ValenceClient(api_key="test_key")
        result = client.asynch._upload_part("https://s3.aws.com/upload", b"test_data", 1)

        assert result == {"ETag": "test_etag_123", "PartNumber": 1}
        mock_put.assert_called_once_with(
            "https://s3.aws.com/upload",
            data=b"test_data",
            headers={"Content-Length": "9"}
        )

    @patch('valenceai.client.requests.put')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_upload_part_failure(self, mock_put):
        """Test part upload failure."""
        mock_put.side_effect = Exception("Upload failed")

        client = ValenceClient(api_key="test_key")

        with pytest.raises(UploadError):
            client.asynch._upload_part("https://s3.aws.com/upload", b"test_data", 1)
    
    @patch('valenceai.client.requests.get')
    @patch('valenceai.client.time.sleep')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_emotions_success(self, mock_sleep, mock_get):
        """Test successful emotion retrieval with timeline data."""
        # Mock successful response on first try
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "emotions": [
                {
                    "timestamp": 0.5,
                    "start_time": 0.0,
                    "end_time": 1.0,
                    "emotion": "happy",
                    "confidence": 0.9,
                    "all_predictions": {"happy": 0.9, "sad": 0.1}
                },
                {
                    "timestamp": 1.5,
                    "start_time": 1.0,
                    "end_time": 2.0,
                    "emotion": "sad",
                    "confidence": 0.8,
                    "all_predictions": {"happy": 0.2, "sad": 0.8}
                }
            ],
            "status": "completed"
        }
        mock_get.return_value = mock_response

        client = ValenceClient(api_key="test_key")
        result = client.asynch.emotions("test_request_id")

        # Verify new v1 endpoint is used
        call_args = mock_get.call_args
        assert '/v1/asynch/emotion/status/test_request_id' in call_args[0][0]

        # Check result includes timeline data
        assert result["status"] == "completed"
        assert len(result["emotions"]) == 2
        assert result["emotions"][0]["emotion"] == "happy"
        mock_sleep.assert_called_once_with(5)  # interval_seconds default
    
    @patch('valenceai.client.requests.get')
    @patch('valenceai.client.time.sleep')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_emotions_retry_then_success(self, mock_sleep, mock_get):
        """Test emotion retrieval with retry then success."""
        # Mock responses: first two fail, third succeeds
        mock_response_fail = Mock()
        mock_response_fail.status_code = 202  # Processing

        mock_response_success = Mock()
        mock_response_success.status_code = 200
        mock_response_success.json.return_value = {"emotions": [], "status": "completed"}

        mock_get.side_effect = [mock_response_fail, mock_response_fail, mock_response_success]

        client = ValenceClient(api_key="test_key")
        result = client.asynch.emotions("test_request_id", max_attempts=5, interval_seconds=1)

        assert result["status"] == "completed"
        assert mock_get.call_count == 3
        assert mock_sleep.call_count == 3  # Called before each attempt

    @patch('valenceai.client.requests.get')
    @patch('valenceai.client.time.sleep')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_emotions_max_retries_exceeded(self, mock_sleep, mock_get):
        """Test emotion retrieval when max retries exceeded."""
        # Mock response that never succeeds
        mock_response = Mock()
        mock_response.status_code = 202  # Still processing
        mock_get.return_value = mock_response

        client = ValenceClient(api_key="test_key")

        with pytest.raises(PredictionError, match="Failed to fetch prediction after retries"):
            client.asynch.emotions("test_request_id", max_attempts=3, interval_seconds=1)

        assert mock_get.call_count == 3

    @patch('valenceai.client.requests.get')
    @patch('valenceai.client.time.sleep')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_emotions_custom_parameters(self, mock_sleep, mock_get):
        """Test emotion retrieval with custom wait and retry parameters."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"emotions": [], "status": "completed"}
        mock_get.return_value = mock_response

        client = ValenceClient(api_key="test_key")
        client.asynch.emotions("test_request_id", max_attempts=5, interval_seconds=10)

        # Verify custom parameters are used
        mock_sleep.assert_called_with(10)

    @patch('valenceai.client.requests.get')
    @patch('valenceai.client.time.sleep')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_get_timeline(self, mock_sleep, mock_get):
        """Test getting timeline data from completed async request."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "emotions": [
                {"timestamp": 0.5, "emotion": "happy", "confidence": 0.9},
                {"timestamp": 1.5, "emotion": "sad", "confidence": 0.8}
            ],
            "status": "completed"
        }
        mock_get.return_value = mock_response

        client = ValenceClient(api_key="test_key")
        timeline = client.asynch.get_timeline("test_request_id")

        assert len(timeline) == 2
        assert timeline[0]["emotion"] == "happy"
        assert timeline[1]["emotion"] == "sad"

    @patch('valenceai.client.requests.get')
    @patch('valenceai.client.time.sleep')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_get_emotion_at_time(self, mock_sleep, mock_get):
        """Test getting emotion at specific timestamp."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "emotions": [
                {"timestamp": 0.5, "start_time": 0.0, "end_time": 1.0, "emotion": "happy"},
                {"timestamp": 1.5, "start_time": 1.0, "end_time": 2.0, "emotion": "sad"}
            ],
            "status": "completed"
        }
        mock_get.return_value = mock_response

        client = ValenceClient(api_key="test_key")
        emotion = client.asynch.get_emotion_at_time("test_request_id", 0.7)

        assert emotion["emotion"] == "happy"

    @patch('valenceai.client.requests.get')
    @patch('valenceai.client.time.sleep')
    @patch('valenceai.config.VALENCE_API_KEY', 'test_key')
    def test_get_dominant_emotion(self, mock_sleep, mock_get):
        """Test getting dominant emotion from timeline."""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "emotions": [
                {"timestamp": 0.5, "emotion": "happy"},
                {"timestamp": 1.5, "emotion": "happy"},
                {"timestamp": 2.5, "emotion": "sad"}
            ],
            "status": "completed"
        }
        mock_get.return_value = mock_response

        client = ValenceClient(api_key="test_key")
        dominant = client.asynch.get_dominant_emotion("test_request_id")

        assert dominant == "happy"

    @patch('valenceai.client.requests.post')
    @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_upload_audio_too_short_error(self, mock_file, mock_getsize, mock_post):
        """Test that AudioTooShortError is raised during upload when audio is too short."""
        mock_getsize.return_value = 1000  # Small file

        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.00 seconds",
            "error_code": "AUDIO_TOO_SHORT",
            "min_duration_seconds": 4.5,
            "actual_duration_seconds": 1.0
        }
        mock_post.return_value = mock_response

        client = ValenceClient(api_key="test_key")

        with pytest.raises(AudioTooShortError) as exc_info:
            client.asynch.upload("short_audio.wav")

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

    @patch('valenceai.client.requests.post')
    @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_upload_audio_too_long_error(self, mock_file, mock_getsize, mock_post):
        """Test that AudioTooLongError is raised during upload when audio is too long."""
        mock_getsize.return_value = 2000000000  # Large file

        mock_response = Mock()
        mock_response.status_code = 400
        mock_response.json.return_value = {
            "error": "Audio file exceeds maximum duration. Maximum: 3600 seconds, provided: 5000 seconds",
            "error_code": "AUDIO_TOO_LONG",
            "max_duration_seconds": 3600,
            "actual_duration_seconds": 5000
        }
        mock_post.return_value = mock_response

        client = ValenceClient(api_key="test_key")

        with pytest.raises(AudioTooLongError) as exc_info:
            client.asynch.upload("long_audio.wav")

        assert exc_info.value.max_duration == 3600
        assert exc_info.value.actual_duration == 5000
        assert "exceeds maximum" in str(exc_info.value)