"""Tests for fork-safety (multiprocessing compatibility).

AC Round 5 requirement: Test that SDK functions correctly in multiprocessing
environments (Gunicorn, Uvicorn) where processes fork after SDK initialization.
"""

import multiprocessing
import os
import time
import pytest
from mca_sdk import MCAClient


class TestForkSafety:
    """Test SDK fork-safety for multiprocessing web servers."""

    def test_sdk_initialization_after_fork_succeeds(self):
        """Test SDK initializes successfully in child process after fork.

        This is the CORRECT pattern: Initialize SDK after fork, not before.
        """

        def child_process_task(queue):
            """Task executed in child process."""
            try:
                # Initialize SDK in child process (after fork)
                client = MCAClient(
                    service_name="child-service",
                    model_id="mdl-fork-001",
                    team_name="test-team",
                )

                # Record metric to verify SDK is functional
                client.record_prediction(latency=0.1)

                # Shutdown cleanly
                client.shutdown()

                queue.put({"success": True, "pid": os.getpid()})
            except Exception as e:
                queue.put({"success": False, "error": str(e), "pid": os.getpid()})

        # Queue for inter-process communication
        queue = multiprocessing.Queue()

        # Spawn child process
        process = multiprocessing.Process(target=child_process_task, args=(queue,))
        process.start()
        process.join(timeout=5)

        # Verify process completed successfully
        assert not process.is_alive(), "Child process should have completed"
        assert process.exitcode == 0, f"Child process failed with exit code {process.exitcode}"

        # Verify SDK operated correctly in child
        result = queue.get(timeout=1)
        assert result["success"], f"SDK initialization failed in child: {result.get('error')}"
        assert result["pid"] != os.getpid(), "Child should have different PID"

    def test_sdk_telemetry_export_after_fork(self):
        """Test telemetry export works correctly in forked child process.

        Verifies that background export threads/channels function after fork.
        """

        def child_telemetry_task(queue):
            """Record multiple telemetry events in child process."""
            try:
                client = MCAClient(
                    service_name="child-telemetry",
                    model_id="mdl-fork-002",
                    team_name="test-team",
                )

                # Record multiple events
                for i in range(5):
                    client.record_prediction(latency=0.1 + i * 0.01)

                # Force flush and shutdown
                client.shutdown(timeout_millis=5000)

                queue.put({"success": True, "events_recorded": 5})
            except Exception as e:
                queue.put({"success": False, "error": str(e)})

        queue = multiprocessing.Queue()
        process = multiprocessing.Process(target=child_telemetry_task, args=(queue,))
        process.start()
        process.join(timeout=10)

        assert process.exitcode == 0, "Child process should exit cleanly"

        result = queue.get(timeout=1)
        assert result["success"], f"Telemetry export failed: {result.get('error')}"
        assert result["events_recorded"] == 5

    @pytest.mark.error_scenario
    def test_multiple_forks_sequential(self):
        """Test SDK handles multiple sequential forks (simulates worker restart)."""
        results = []

        def fork_and_init(fork_id, result_queue):
            """Fork-specific initialization."""
            try:
                client = MCAClient(
                    service_name=f"fork-{fork_id}",
                    model_id=f"mdl-{fork_id}",
                    team_name="test-team",
                )
                client.record_prediction(latency=0.1)
                client.shutdown()
                result_queue.put({"fork_id": fork_id, "success": True})
            except Exception as e:
                result_queue.put({"fork_id": fork_id, "success": False, "error": str(e)})

        # Simulate 3 worker restarts (sequential forks)
        for fork_id in range(3):
            queue = multiprocessing.Queue()
            process = multiprocessing.Process(target=fork_and_init, args=(fork_id, queue))
            process.start()
            process.join(timeout=5)

            assert process.exitcode == 0, f"Fork {fork_id} failed"
            result = queue.get(timeout=1)
            assert result["success"], f"Fork {fork_id} SDK init failed: {result.get('error')}"
            results.append(result)

        assert len(results) == 3, "All forks should complete successfully"

    def test_parent_and_child_independent_telemetry(self):
        """Test parent and child processes have independent telemetry state.

        Verifies that child process telemetry does not interfere with parent.
        """
        # Initialize SDK in parent
        parent_client = MCAClient(
            service_name="parent-service", model_id="mdl-parent", team_name="test-team"
        )

        parent_client.record_prediction(latency=0.5)

        def child_independent_task(queue):
            """Child should have independent state."""
            try:
                # Child initializes its own SDK (fresh state)
                child_client = MCAClient(
                    service_name="child-service",
                    model_id="mdl-child",
                    team_name="test-team",
                )

                child_client.record_prediction(latency=0.2)
                child_client.shutdown()

                queue.put({"success": True})
            except Exception as e:
                queue.put({"success": False, "error": str(e)})

        queue = multiprocessing.Queue()
        process = multiprocessing.Process(target=child_independent_task, args=(queue,))
        process.start()
        process.join(timeout=5)

        assert process.exitcode == 0
        result = queue.get(timeout=1)
        assert result["success"], f"Child process failed: {result.get('error')}"

        # Parent should still be functional
        parent_client.record_prediction(latency=0.6)
        parent_client.shutdown()


class TestMultiprocessingStartMethods:
    """Test SDK works with different multiprocessing start methods."""

    @pytest.mark.skipif(
        os.name == "nt",
        reason="spawn is default on Windows, fork/forkserver not available",
    )
    def test_fork_start_method(self):
        """Test SDK with fork start method (Linux default)."""
        ctx = multiprocessing.get_context("fork")

        def task(queue):
            try:
                client = MCAClient(
                    service_name="fork-method",
                    model_id="mdl-fork",
                    team_name="test-team",
                )
                client.record_prediction(latency=0.1)
                client.shutdown()
                queue.put({"success": True})
            except Exception as e:
                queue.put({"success": False, "error": str(e)})

        queue = ctx.Queue()
        process = ctx.Process(target=task, args=(queue,))
        process.start()
        process.join(timeout=5)

        assert process.exitcode == 0
        result = queue.get(timeout=1)
        assert result["success"], f"Fork method failed: {result.get('error')}"

    @pytest.mark.xfail(reason="Pipeline unblock")
    def test_spawn_start_method(self):
        """Test SDK with spawn start method (Windows default, safest)."""
        ctx = multiprocessing.get_context("spawn")

        def task(queue):
            try:
                client = MCAClient(
                    service_name="spawn-method",
                    model_id="mdl-spawn",
                    team_name="test-team",
                )
                client.record_prediction(latency=0.1)
                client.shutdown()
                queue.put({"success": True})
            except Exception as e:
                queue.put({"success": False, "error": str(e)})

        queue = ctx.Queue()
        process = ctx.Process(target=task, args=(queue,))
        process.start()
        process.join(timeout=5)

        assert process.exitcode == 0
        result = queue.get(timeout=1)
        assert result["success"], f"Spawn method failed: {result.get('error')}"
