#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.

"""
Integration tests for gunicorn's uWSGI binary protocol with nginx.

These tests verify that gunicorn correctly implements the uWSGI binary
protocol by running actual requests through nginx's uwsgi_pass directive.
"""

import concurrent.futures
import json

import pytest
import requests

from conftest import docker_available


@docker_available
class TestBasicRequests:
    """Test basic HTTP request handling through uWSGI protocol."""

    def test_get_root(self, nginx_url):
        """Test basic GET request to root endpoint."""
        response = requests.get(f'{nginx_url}/')
        assert response.status_code == 200
        assert b'Hello from gunicorn uWSGI!' in response.content

    def test_get_with_query_string(self, nginx_url):
        """Test GET request with query string parameters."""
        response = requests.get(f'{nginx_url}/query?foo=bar&baz=qux')
        assert response.status_code == 200
        data = response.json()
        assert data['foo'] == 'bar'
        assert data['baz'] == 'qux'

    def test_post_echo(self, nginx_url):
        """Test POST request with body echo."""
        test_body = b'This is a test body content'
        response = requests.post(f'{nginx_url}/echo', data=test_body)
        assert response.status_code == 200
        assert response.content == test_body

    def test_post_json(self, nginx_url):
        """Test POST request with JSON body."""
        test_data = {'key': 'value', 'number': 42, 'nested': {'a': 1}}
        response = requests.post(
            f'{nginx_url}/json',
            json=test_data,
            headers={'Content-Type': 'application/json'}
        )
        assert response.status_code == 200
        data = response.json()
        assert data['status'] == 'ok'
        assert data['received'] == test_data

    def test_post_large_body(self, nginx_url):
        """Test POST with large request body (100KB)."""
        large_body = b'X' * (100 * 1024)
        response = requests.post(f'{nginx_url}/echo', data=large_body)
        assert response.status_code == 200
        assert len(response.content) == len(large_body)
        assert response.content == large_body


@docker_available
class TestHeaderPreservation:
    """Test that headers are correctly passed through uWSGI protocol."""

    def test_custom_headers(self, nginx_url):
        """Test custom headers are passed to the application."""
        custom_headers = {
            'X-Custom-Header': 'custom-value',
            'X-Another-Header': 'another-value'
        }
        response = requests.get(f'{nginx_url}/headers', headers=custom_headers)
        assert response.status_code == 200
        data = response.json()
        assert data.get('X-Custom-Header') == 'custom-value'
        assert data.get('X-Another-Header') == 'another-value'

    def test_host_header(self, nginx_url):
        """Test Host header is passed correctly."""
        response = requests.get(
            f'{nginx_url}/headers',
            headers={'Host': 'test.example.com'}
        )
        assert response.status_code == 200
        data = response.json()
        assert data.get('Host') == 'test.example.com'

    def test_content_type_header(self, nginx_url):
        """Test Content-Type header is passed correctly."""
        response = requests.post(
            f'{nginx_url}/headers',
            data='test',
            headers={'Content-Type': 'application/x-custom-type'}
        )
        assert response.status_code == 200
        data = response.json()
        assert data.get('Content-Type') == 'application/x-custom-type'

    def test_user_agent_header(self, nginx_url):
        """Test User-Agent header is passed correctly."""
        response = requests.get(
            f'{nginx_url}/headers',
            headers={'User-Agent': 'TestAgent/1.0'}
        )
        assert response.status_code == 200
        data = response.json()
        assert data.get('User-Agent') == 'TestAgent/1.0'


@docker_available
class TestKeepAlive:
    """Test HTTP keep-alive with multiple requests per connection."""

    def test_multiple_requests_same_session(self, session, nginx_url):
        """Test multiple requests using same session/connection."""
        for i in range(5):
            response = session.get(f'{nginx_url}/')
            assert response.status_code == 200

    def test_mixed_requests_same_session(self, session, nginx_url):
        """Test mixed GET and POST requests using same session."""
        # GET request
        response = session.get(f'{nginx_url}/')
        assert response.status_code == 200

        # POST request
        response = session.post(f'{nginx_url}/echo', data=b'test')
        assert response.status_code == 200
        assert response.content == b'test'

        # Another GET
        response = session.get(f'{nginx_url}/headers')
        assert response.status_code == 200

        # JSON POST
        response = session.post(f'{nginx_url}/json', json={'test': 1})
        assert response.status_code == 200


@docker_available
class TestErrorResponses:
    """Test HTTP error responses through uWSGI protocol."""

    @pytest.mark.parametrize('code', [400, 401, 403, 404, 500, 502, 503])
    def test_error_codes(self, nginx_url, code):
        """Test various HTTP error codes are returned correctly."""
        response = requests.get(f'{nginx_url}/error/{code}')
        assert response.status_code == code
        data = response.json()
        assert data['code'] == code

    def test_not_found(self, nginx_url):
        """Test 404 for non-existent path."""
        response = requests.get(f'{nginx_url}/nonexistent/path')
        assert response.status_code == 404
        data = response.json()
        assert data['error'] == 'Not Found'
        assert data['path'] == '/nonexistent/path'


@docker_available
class TestEnvironVariables:
    """Test WSGI environ variables are correctly set."""

    def test_request_method(self, nginx_url):
        """Test REQUEST_METHOD is set correctly."""
        response = requests.get(f'{nginx_url}/environ')
        assert response.status_code == 200
        data = response.json()
        assert data.get('REQUEST_METHOD') == 'GET'

        response = requests.post(f'{nginx_url}/environ', data='')
        data = response.json()
        assert data.get('REQUEST_METHOD') == 'POST'

    def test_path_info(self, nginx_url):
        """Test PATH_INFO is set correctly."""
        response = requests.get(f'{nginx_url}/environ')
        assert response.status_code == 200
        data = response.json()
        assert data.get('PATH_INFO') == '/environ'

    def test_query_string(self, nginx_url):
        """Test QUERY_STRING is set correctly."""
        response = requests.get(f'{nginx_url}/environ?foo=bar&test=123')
        assert response.status_code == 200
        data = response.json()
        assert data.get('QUERY_STRING') == 'foo=bar&test=123'

    def test_server_protocol(self, nginx_url):
        """Test SERVER_PROTOCOL is set."""
        response = requests.get(f'{nginx_url}/environ')
        assert response.status_code == 200
        data = response.json()
        assert 'SERVER_PROTOCOL' in data
        assert data['SERVER_PROTOCOL'].startswith('HTTP/')

    def test_content_length(self, nginx_url):
        """Test CONTENT_LENGTH is set for POST requests."""
        body = 'test body content'
        response = requests.post(f'{nginx_url}/environ', data=body)
        assert response.status_code == 200
        data = response.json()
        assert data.get('CONTENT_LENGTH') == str(len(body))


@docker_available
class TestLargeResponses:
    """Test large response handling through uWSGI protocol."""

    def test_1mb_response(self, nginx_url):
        """Test 1MB response body is received correctly."""
        response = requests.get(f'{nginx_url}/large')
        assert response.status_code == 200
        assert len(response.content) == 1024 * 1024
        # Verify content is all 'X' characters
        assert response.content == b'X' * (1024 * 1024)

    def test_large_response_content_length(self, nginx_url):
        """Test Content-Length header for large response."""
        response = requests.get(f'{nginx_url}/large')
        assert response.status_code == 200
        assert response.headers.get('Content-Length') == str(1024 * 1024)


@docker_available
class TestConcurrency:
    """Test concurrent request handling."""

    def test_parallel_requests(self, nginx_url):
        """Test handling multiple parallel requests."""
        num_requests = 20

        def make_request(i):
            response = requests.get(f'{nginx_url}/query?id={i}')
            return response.status_code, response.json().get('id')

        with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
            futures = [executor.submit(make_request, i) for i in range(num_requests)]
            results = [f.result() for f in concurrent.futures.as_completed(futures)]

        # All requests should succeed
        assert all(status == 200 for status, _ in results)
        # All IDs should be present
        ids = set(id_val for _, id_val in results)
        assert ids == set(str(i) for i in range(num_requests))

    def test_parallel_mixed_requests(self, nginx_url):
        """Test parallel GET and POST requests."""
        def get_request():
            return requests.get(f'{nginx_url}/').status_code

        def post_request(data):
            response = requests.post(f'{nginx_url}/echo', data=data)
            return response.status_code, response.content

        with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
            get_futures = [executor.submit(get_request) for _ in range(10)]
            post_futures = [
                executor.submit(post_request, f'data-{i}'.encode())
                for i in range(10)
            ]

            get_results = [f.result() for f in get_futures]
            post_results = [f.result() for f in post_futures]

        assert all(status == 200 for status in get_results)
        assert all(status == 200 for status, _ in post_results)


@docker_available
class TestSpecialCases:
    """Test edge cases and special scenarios."""

    def test_empty_body_post(self, nginx_url):
        """Test POST with empty body."""
        response = requests.post(f'{nginx_url}/echo', data=b'')
        assert response.status_code == 200
        assert response.content == b''

    def test_binary_body(self, nginx_url):
        """Test POST with binary body containing null bytes."""
        binary_data = bytes(range(256))
        response = requests.post(f'{nginx_url}/echo', data=binary_data)
        assert response.status_code == 200
        assert response.content == binary_data

    def test_unicode_in_query_string(self, nginx_url):
        """Test unicode characters in query string."""
        response = requests.get(f'{nginx_url}/query', params={'name': 'test'})
        assert response.status_code == 200
        data = response.json()
        assert data.get('name') == 'test'

    def test_special_characters_in_path(self, nginx_url):
        """Test handling of special path that triggers 404."""
        # This should return 404 since the path doesn't exist
        response = requests.get(f'{nginx_url}/path/with/slashes')
        assert response.status_code == 404

    def test_long_header_value(self, nginx_url):
        """Test handling of long header values."""
        long_value = 'X' * 4096  # 4KB header value
        response = requests.get(
            f'{nginx_url}/headers',
            headers={'X-Long-Header': long_value}
        )
        assert response.status_code == 200
        data = response.json()
        assert data.get('X-Long-Header') == long_value
