from unittest.mock import MagicMock, patch

import pytest
from requests.exceptions import ConnectionError, Timeout

from redenlab_extract.api import (
    check_health,
    get_job_status,
    request_presigned_url,
    submit_inference_job,
)
from redenlab_extract.exceptions import APIError, AuthenticationError, InferenceError


class TestRequestPresignedUrl:

    @patch("redenlab_extract.api.requests.request")
    def test_request_presigned_url_success(self, mock_request):
        """Test request_presigned_url returns asset_id, url, expires."""
        # Mock the HTTP response
        mock_response = MagicMock()
        mock_response.ok = True
        mock_response.json.return_value = {
            "asset_id": "asset-123",
            "upload_url": "https://s3.amazonaws.com/bucket/path?signature=xyz",
            "expires_in": 3600,
        }
        mock_request.return_value = mock_response

        # Call the function
        asset_id, upload_url, expires_in = request_presigned_url(
            base_url="https://api.test.example.com",
            api_key="sk_test_12345",
            filename="test.wav",
            content_type="audio/wav",
        )

        # Verify
        assert asset_id == "asset-123"
        assert upload_url == "https://s3.amazonaws.com/bucket/path?signature=xyz"
        assert expires_in == 3600

        # Verify the request was made correctly
        mock_request.assert_called_once()
        call_kwargs = mock_request.call_args.kwargs
        assert "https://api.test.example.com/upload-url" in call_kwargs["url"]
        assert call_kwargs["headers"]["X-Api-Key"] == "sk_test_12345"

    @patch("redenlab_extract.api.requests.request")
    def test_request_presigned_url_with_chunk_metadata(self, mock_request):
        """Test request_presigned_url includes parent_asset_id and chunk_index."""
        # Mock the HTTP response
        mock_response = MagicMock()
        mock_response.ok = True
        mock_response.json.return_value = {
            "asset_id": "chunk-asset-456",
            "upload_url": "https://s3.amazonaws.com/bucket/chunk?signature=abc",
            "expires_in": 3600,
        }
        mock_request.return_value = mock_response

        # Call the function with chunk metadata
        asset_id, upload_url, expires_in = request_presigned_url(
            base_url="https://api.test.example.com",
            api_key="sk_test_12345",
            filename="test_chunk_0.wav",
            content_type="audio/wav",
            parent_asset_id="parent-asset-789",
            chunk_index=0,
        )

        # Verify
        assert asset_id == "chunk-asset-456"

        # Verify the request body includes chunk metadata
        call_kwargs = mock_request.call_args.kwargs
        request_body = call_kwargs["json"]
        assert request_body["parent_asset_id"] == "parent-asset-789"
        assert request_body["chunk_index"] == 0


class TestSubmitInferenceJob:

    @patch("redenlab_extract.api.requests.request")
    def test_submit_inference_job_success(self, mock_request):
        """Test submit_inference_job returns job_id."""
        # Mock the HTTP response
        mock_response = MagicMock()
        mock_response.ok = True
        mock_response.json.return_value = {
            "job_id": "job-123",
            "status": "submitted",
            "message": "Job submitted successfully",
        }
        mock_request.return_value = mock_response

        # Call the function
        result = submit_inference_job(
            base_url="https://api.test.example.com",
            api_key="sk_test_12345",
            model_name="als-intelligibility-mtpa",
            payload={"asset_id": "asset-123"},
        )

        # Verify
        assert result["job_id"] == "job-123"
        assert result["status"] == "submitted"

        # Verify the request was made correctly
        mock_request.assert_called_once()
        call_kwargs = mock_request.call_args.kwargs
        assert "als-intelligibility-mtpa/predict" in call_kwargs["url"]


class TestGetJobStatus:

    @patch("redenlab_extract.api.requests.get")
    def test_get_job_status_success(self, mock_get):
        """Test get_job_status returns status dict."""
        # Mock the HTTP response
        mock_response = MagicMock()
        mock_response.ok = True
        mock_response.json.return_value = {
            "job_id": "job-123",
            "status": "completed",
            "result": {"intelligibility_score": 0.85},
            "created_at": "2024-01-01T00:00:00Z",
            "completed_at": "2024-01-01T00:01:00Z",
        }
        mock_get.return_value = mock_response

        # Call the function
        status = get_job_status(
            base_url="https://api.test.example.com",
            api_key="sk_test_12345",
            job_id="job-123",
            model_name="als-intelligibility-mtpa",
        )

        # Verify
        assert status["job_id"] == "job-123"
        assert status["status"] == "completed"
        assert status["result"]["intelligibility_score"] == 0.85

        # Verify request was made correctly
        mock_get.assert_called_once()
        call_kwargs = mock_get.call_args.kwargs
        assert call_kwargs["params"]["model"] == "als-intelligibility-mtpa"


class TestCheckHealth:

    @patch("redenlab_extract.api.requests.get")
    def test_check_health_success(self, mock_get):
        """Test check_health returns health dict."""
        # Mock the HTTP response
        mock_response = MagicMock()
        mock_response.ok = True
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "status": "ready",
            "ready": True,
        }
        mock_get.return_value = mock_response

        # Call the function
        health = check_health(
            base_url="https://api.test.example.com",
            api_key="sk_test_12345",
            model_name="als-intelligibility-mtpa",
            timeout=60,
        )

        # Verify
        assert health["status"] == "ready"
        assert health["ready"] is True
        assert "response_time" in health
        assert "is_warming" in health


class TestErrorHandling:

    @patch("redenlab_extract.api.requests.request")
    def test_api_401_raises_auth_error(self, mock_request):
        """Test that 401 response maps to AuthenticationError."""
        # Mock 401 response
        mock_response = MagicMock()
        mock_response.ok = False
        mock_response.status_code = 401
        mock_response.json.return_value = {"error": "Invalid API key"}
        mock_request.return_value = mock_response

        # Verify AuthenticationError is raised
        with pytest.raises(AuthenticationError, match="Authentication failed"):
            request_presigned_url(
                base_url="https://api.test.example.com",
                api_key="invalid_key",
                filename="test.wav",
            )

    @patch("redenlab_extract.api.requests.request")
    def test_api_403_raises_auth_error(self, mock_request):
        """Test that 403 response maps to AuthenticationError."""
        # Mock 403 response
        mock_response = MagicMock()
        mock_response.ok = False
        mock_response.status_code = 403
        mock_response.json.return_value = {"error": "Forbidden"}
        mock_request.return_value = mock_response

        # Verify AuthenticationError is raised
        with pytest.raises(AuthenticationError, match="Authentication failed"):
            request_presigned_url(
                base_url="https://api.test.example.com",
                api_key="sk_test_12345",
                filename="test.wav",
            )

    @patch("redenlab_extract.api.requests.request")
    def test_api_400_raises_inference_error(self, mock_request):
        """Test that 400 response maps to InferenceError."""
        # Mock 400 response
        mock_response = MagicMock()
        mock_response.ok = False
        mock_response.status_code = 400
        mock_response.json.return_value = {"error": "Invalid payload"}
        mock_request.return_value = mock_response

        # Verify InferenceError is raised
        with pytest.raises(InferenceError, match="Invalid request"):
            submit_inference_job(
                base_url="https://api.test.example.com",
                api_key="sk_test_12345",
                model_name="als-intelligibility-mtpa",
                payload={},
            )

    @patch("redenlab_extract.api.requests.request")
    def test_api_422_raises_inference_error(self, mock_request):
        """Test that 422 response maps to InferenceError."""
        # Mock 422 response
        mock_response = MagicMock()
        mock_response.ok = False
        mock_response.status_code = 422
        mock_response.json.return_value = {"error": "Validation failed"}
        mock_request.return_value = mock_response

        # Verify InferenceError is raised
        with pytest.raises(InferenceError, match="Invalid request"):
            submit_inference_job(
                base_url="https://api.test.example.com",
                api_key="sk_test_12345",
                model_name="als-intelligibility-mtpa",
                payload={},
            )

    @patch("redenlab_extract.api.requests.request")
    def test_api_500_raises_api_error(self, mock_request):
        """Test that 5xx response maps to APIError."""
        # Mock 500 response
        mock_response = MagicMock()
        mock_response.ok = False
        mock_response.status_code = 500
        mock_response.json.return_value = {"error": "Internal server error"}
        mock_request.return_value = mock_response

        # Verify APIError is raised
        with pytest.raises(APIError, match="Server error"):
            request_presigned_url(
                base_url="https://api.test.example.com",
                api_key="sk_test_12345",
                filename="test.wav",
            )

    @patch("redenlab_extract.api.requests.request")
    def test_api_502_raises_api_error(self, mock_request):
        """Test that 502 response maps to APIError."""
        # Mock 502 response
        mock_response = MagicMock()
        mock_response.ok = False
        mock_response.status_code = 502
        mock_response.json.return_value = {"error": "Bad gateway"}
        mock_request.return_value = mock_response

        # Verify APIError is raised
        with pytest.raises(APIError, match="Server error"):
            request_presigned_url(
                base_url="https://api.test.example.com",
                api_key="sk_test_12345",
                filename="test.wav",
            )

    @patch("redenlab_extract.api.requests.request")
    def test_api_connection_error_wrapped_as_api_error(self, mock_request):
        """Test that ConnectionError is wrapped in APIError.

        Note: Retry logic is handled by the @retry decorator but is complex to test
        in unit tests. This test verifies error wrapping behavior.
        """
        # Mock raises ConnectionError
        mock_request.side_effect = ConnectionError("Connection refused")

        # Verify it raises APIError (wrapping the connection error)
        with pytest.raises(APIError, match="Connection error"):
            request_presigned_url(
                base_url="https://api.test.example.com",
                api_key="sk_test_12345",
                filename="test.wav",
            )

    @patch("redenlab_extract.api.requests.request")
    def test_api_timeout_error_wrapped_as_api_error(self, mock_request):
        """Test that Timeout is wrapped in APIError.

        Note: Retry logic is handled by the @retry decorator but is complex to test
        in unit tests. This test verifies error wrapping behavior.
        """
        # Mock raises Timeout
        mock_request.side_effect = Timeout("Request timed out")

        # Verify it raises APIError (wrapping the timeout)
        with pytest.raises(APIError, match="Request timed out"):
            request_presigned_url(
                base_url="https://api.test.example.com",
                api_key="sk_test_12345",
                filename="test.wav",
            )
