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

#!/usr/bin/env python
"""
Tests for HTTP/2 with gevent example.

Run with:
    # Start the server first
    docker compose up -d

    # Run tests
    python test_http2_gevent.py

    # Or with pytest
    pytest test_http2_gevent.py -v

Requirements:
    pip install httpx[http2] pytest pytest-asyncio
"""

import asyncio
import sys
import ssl
import socket
import time


def check_server_available(host='localhost', port=8443, timeout=30):
    """Wait for server to become available."""
    start = time.time()
    while time.time() - start < timeout:
        try:
            ctx = ssl.create_default_context()
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE
            with socket.create_connection((host, port), timeout=2) as sock:
                with ctx.wrap_socket(sock, server_hostname=host):
                    return True
        except (socket.error, ssl.SSLError, OSError):
            time.sleep(1)
    return False


class TestHTTP2Gevent:
    """Test HTTP/2 functionality with gevent worker."""

    BASE_URL = "https://localhost:8443"

    @classmethod
    def setup_class(cls):
        """Check server is available before running tests."""
        if not check_server_available():
            raise RuntimeError(
                "Server not available. Start it with: docker compose up -d"
            )

    def get_client(self):
        """Create HTTP/2 client."""
        import httpx
        return httpx.Client(http2=True, verify=False, timeout=30.0)

    def test_root_endpoint(self):
        """Test basic GET request returns HTTP/2."""
        with self.get_client() as client:
            response = client.get(f"{self.BASE_URL}/")

            assert response.status_code == 200
            assert response.http_version == "HTTP/2"
            assert b"HTTP/2" in response.content or b"Gevent" in response.content

    def test_health_endpoint(self):
        """Test health check endpoint."""
        with self.get_client() as client:
            response = client.get(f"{self.BASE_URL}/health")

            assert response.status_code == 200
            assert response.text == "OK"

    def test_echo_post(self):
        """Test POST echo endpoint."""
        with self.get_client() as client:
            data = b"Hello HTTP/2 with Gevent!"
            response = client.post(f"{self.BASE_URL}/echo", content=data)

            assert response.status_code == 200
            assert response.content == data

    def test_echo_large_body(self):
        """Test POST with large body (tests flow control)."""
        with self.get_client() as client:
            # 100KB of data
            data = b"X" * (100 * 1024)
            response = client.post(f"{self.BASE_URL}/echo", content=data)

            assert response.status_code == 200
            assert len(response.content) == len(data)
            assert response.content == data

    def test_info_endpoint(self):
        """Test JSON info endpoint."""
        with self.get_client() as client:
            response = client.get(f"{self.BASE_URL}/info")

            assert response.status_code == 200
            info = response.json()
            assert info['method'] == 'GET'
            assert info['path'] == '/info'
            assert 'gevent' in info['server'].lower()

    def test_large_response(self):
        """Test large response (1MB) - tests streaming and flow control."""
        with self.get_client() as client:
            response = client.get(f"{self.BASE_URL}/large")

            assert response.status_code == 200
            assert len(response.content) == 1024 * 1024
            assert response.content == b"X" * (1024 * 1024)

    def test_streaming_response(self):
        """Test server-sent events style streaming."""
        with self.get_client() as client:
            response = client.get(f"{self.BASE_URL}/stream")

            assert response.status_code == 200
            assert b"chunk 0" in response.content
            assert b"chunk 9" in response.content

    def test_delay_endpoint(self):
        """Test delayed response."""
        with self.get_client() as client:
            start = time.time()
            response = client.get(f"{self.BASE_URL}/delay?seconds=0.5")
            elapsed = time.time() - start

            assert response.status_code == 200
            assert elapsed >= 0.4  # Allow some tolerance
            assert b"Delayed" in response.content

    def test_not_found(self):
        """Test 404 response."""
        with self.get_client() as client:
            response = client.get(f"{self.BASE_URL}/nonexistent")

            assert response.status_code == 404

    def test_gevent_worker_header(self):
        """Test that gevent worker header is present."""
        with self.get_client() as client:
            response = client.get(f"{self.BASE_URL}/")

            assert response.status_code == 200
            assert response.headers.get('x-worker-type') == 'gevent'


class TestHTTP2Concurrency:
    """Test HTTP/2 multiplexing with concurrent requests."""

    BASE_URL = "https://localhost:8443"

    @classmethod
    def setup_class(cls):
        """Check server is available."""
        if not check_server_available():
            raise RuntimeError("Server not available")

    def test_concurrent_requests_sync(self):
        """Test multiple concurrent requests using threads."""
        import httpx
        from concurrent.futures import ThreadPoolExecutor, as_completed

        def make_request(i):
            with httpx.Client(http2=True, verify=False, timeout=30.0) as client:
                response = client.get(f"{self.BASE_URL}/delay?seconds=0.2")
                return i, response.status_code

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

        assert len(results) == num_requests
        assert all(status == 200 for _, status in results)


class TestHTTP2ConcurrencyAsync:
    """Async tests for HTTP/2 multiplexing."""

    BASE_URL = "https://localhost:8443"

    @classmethod
    def setup_class(cls):
        """Check server is available."""
        if not check_server_available():
            raise RuntimeError("Server not available")

    def test_async_concurrent_requests(self):
        """Test concurrent requests with asyncio."""
        import httpx

        async def run_concurrent():
            async with httpx.AsyncClient(http2=True, verify=False, timeout=30.0) as client:
                # Make 10 concurrent requests
                tasks = [
                    client.get(f"{self.BASE_URL}/delay?seconds=0.2")
                    for _ in range(10)
                ]
                responses = await asyncio.gather(*tasks)
                return responses

        responses = asyncio.run(run_concurrent())

        assert len(responses) == 10
        assert all(r.status_code == 200 for r in responses)
        assert all(r.http_version == "HTTP/2" for r in responses)

    def test_async_multiple_streams(self):
        """Test that multiple concurrent streams work over single HTTP/2 connection.

        This test verifies that HTTP/2 can handle multiple concurrent requests,
        which is the foundation of multiplexing. Performance benefits depend on
        client library implementation and network conditions.
        """
        import httpx

        async def run_test():
            async with httpx.AsyncClient(http2=True, verify=False, timeout=30.0) as client:
                # Send multiple concurrent requests
                tasks = [
                    client.get(f"{self.BASE_URL}/info")
                    for _ in range(10)
                ]
                responses = await asyncio.gather(*tasks)
                return responses

        responses = asyncio.run(run_test())

        # Verify all requests succeeded with HTTP/2
        assert len(responses) == 10
        assert all(r.status_code == 200 for r in responses)
        assert all(r.http_version == "HTTP/2" for r in responses)


def run_basic_test():
    """Run a basic test without pytest."""
    print("Running basic HTTP/2 gevent test...")

    if not check_server_available():
        print("ERROR: Server not available at https://localhost:8443")
        print("Start it with: docker compose up -d")
        return False

    try:
        import httpx
    except ImportError:
        print("ERROR: httpx not installed. Run: pip install httpx[http2]")
        return False

    try:
        with httpx.Client(http2=True, verify=False, timeout=30.0) as client:
            # Test basic request
            print("  Testing root endpoint...", end=" ")
            response = client.get("https://localhost:8443/")
            assert response.status_code == 200
            assert response.http_version == "HTTP/2"
            print("OK")

            # Test echo
            print("  Testing echo endpoint...", end=" ")
            data = b"test data"
            response = client.post("https://localhost:8443/echo", content=data)
            assert response.content == data
            print("OK")

            # Test large response
            print("  Testing large response...", end=" ")
            response = client.get("https://localhost:8443/large")
            assert len(response.content) == 1024 * 1024
            print("OK")

            # Test worker header
            print("  Testing gevent worker...", end=" ")
            response = client.get("https://localhost:8443/")
            assert response.headers.get('x-worker-type') == 'gevent'
            print("OK")

        print("\nAll basic tests passed!")
        return True

    except Exception as e:
        print(f"\nERROR: {e}")
        return False


if __name__ == '__main__':
    # Check if pytest is available
    try:
        import pytest
        # Run with pytest if available
        sys.exit(pytest.main([__file__, '-v']))
    except ImportError:
        # Run basic tests without pytest
        success = run_basic_test()
        sys.exit(0 if success else 1)
