"""Restore is a distinct operation, never a create call with an ignored field."""

import pytest

from microsandbox import Network, NetworkPolicy, Sandbox, SecurityProfile


@pytest.mark.parametrize(
    "option", ["image", "network", "cmd", "replace", "detached", "entrypoint"],
)
def test_restore_rejects_create_options(option):
    with pytest.raises(TypeError, match="unexpected restore option"):
        Sandbox.restore("missing", name="restore-validation", **{option: None})


@pytest.mark.parametrize(
    "option", ["from_snapshot", "forked", "cow_memory", "disk_only", "snapshot_base"],
)
def test_create_rejects_restore_options(option):
    with pytest.raises(TypeError):
        Sandbox.create("restore-validation", image="alpine", **{option: None})


@pytest.mark.asyncio
@pytest.mark.parametrize("method", ["restore", "restore_with_progress"])
@pytest.mark.parametrize("options", [
    {"forked": True},
    {"forked": False},
    {"forked": True, "cow_memory": True},
])
async def test_legacy_cow_memory_alias_warns(tmp_path, method, options):
    with (
        pytest.warns(DeprecationWarning, match="forked is deprecated; use cow_memory"),
        pytest.raises(FileNotFoundError),
    ):
        result = getattr(Sandbox, method)(
            tmp_path / "missing", name="restore-alias", **options,
        )
        if method == "restore":
            await result
        else:
            await result.result()


@pytest.mark.asyncio
@pytest.mark.filterwarnings("error::DeprecationWarning")
async def test_restore_missing_artifact_does_not_boot(tmp_path):
    with pytest.raises(FileNotFoundError):
        await Sandbox.restore(tmp_path / "missing", name="restore-validation", cow_memory=True)


@pytest.mark.asyncio
@pytest.mark.parametrize("method", ["restore", "restore_with_progress"])
async def test_missing_resource_opt_out_is_restore_only(tmp_path, method):
    with pytest.raises(FileNotFoundError):
        result = getattr(Sandbox, method)(
            tmp_path / "missing", name="restore-opt-out",
            allow_missing_resources=True, external_mount_policy="strict",
        )
        if method == "restore":
            await result
        else:
            await result.result()
    with pytest.raises(TypeError):
        Sandbox.create("fresh", image="alpine", allow_missing_resources=True)


@pytest.mark.asyncio
async def test_restore_accepts_explicit_destination_controls(tmp_path):
    with pytest.raises(FileNotFoundError):
        await Sandbox.restore(
            tmp_path / "missing", name="restore-controls", disk_only=True,
            cpus=2, memory=512, network_policy=NetworkPolicy.none(),
            max_tcp_connections=0, max_udp_connections=7,
            disable_network=True, security=SecurityProfile.DEFAULT, max_duration=0, idle_timeout=0,
        )


@pytest.mark.asyncio
@pytest.mark.parametrize("method", ["restore", "restore_with_progress"])
@pytest.mark.parametrize("limits", [
    {},
    {"max_tcp_connections": None, "max_udp_connections": None},
    {"max_tcp_connections": 0, "max_udp_connections": 7},
    {"max_tcp_connections": 64, "max_udp_connections": 0},
    {"max_connections": None, "max_tcp_connections": 0},
])
async def test_restore_connection_limits_reach_artifact_validation(tmp_path, method, limits):
    # A missing artifact proves these options reach restore, never fresh creation.
    with pytest.raises(FileNotFoundError):
        result = getattr(Sandbox, method)(tmp_path / "missing", name="restore-limits", **limits)
        if method == "restore":
            await result
        else:
            await result.result()


@pytest.mark.asyncio
@pytest.mark.parametrize("method", ["restore", "restore_with_progress"])
async def test_restore_legacy_tcp_alias_warns_and_accepts_udp(tmp_path, method):
    with (
        pytest.warns(DeprecationWarning, match="use max_tcp_connections"),
        pytest.raises(FileNotFoundError),
    ):
        result = getattr(Sandbox, method)(
            tmp_path / "missing", name="restore-limits", max_connections=0, max_udp_connections=7,
        )
        if method == "restore":
            await result
        else:
            await result.result()


@pytest.mark.parametrize("method", ["restore", "restore_with_progress"])
@pytest.mark.parametrize("tcp", [0, 64])
def test_restore_rejects_duplicate_tcp_aliases(method, tcp):
    with pytest.raises(ValueError, match="mutually exclusive"):
        getattr(Sandbox, method)(
            "missing", name="restore-limits", max_connections=0, max_tcp_connections=tcp,
        )


@pytest.mark.parametrize("method", ["restore", "restore_with_progress"])
@pytest.mark.parametrize(
    "option", ["max_connections", "max_tcp_connections", "max_udp_connections"],
)
def test_restore_rejects_negative_connection_limits(method, option):
    with pytest.raises(OverflowError):
        getattr(Sandbox, method)("missing", name="restore-limits", **{option: -1})


@pytest.mark.asyncio
@pytest.mark.parametrize("method", ["restore", "restore_with_progress"])
async def test_restore_accept_queue_size_reaches_artifact_validation(tmp_path, method):
    # A missing artifact proves the option is accepted and reaches restore.
    with pytest.raises(FileNotFoundError):
        result = getattr(Sandbox, method)(
            tmp_path / "missing", name="restore-queue", ports={8080: 80},
            tcp_accept_queue_size=4096,
        )
        if method == "restore":
            await result
        else:
            await result.result()


@pytest.mark.parametrize("method", ["restore", "restore_with_progress"])
@pytest.mark.parametrize(("value", "error"), [
    (-1, OverflowError), (4_294_967_297, OverflowError), (1.5, TypeError),
])
def test_restore_rejects_accept_queue_size_it_cannot_represent(method, value, error):
    # Never wrapped or truncated into a different size.
    with pytest.raises(error):
        getattr(Sandbox, method)("missing", name="restore-queue", tcp_accept_queue_size=value)


def test_restore_policy_rejects_broad_network_configuration():
    with pytest.raises(TypeError):
        Sandbox.restore("missing", name="restore-controls", network_policy=Network.none())


@pytest.mark.parametrize("value", [-1, float("nan"), float("inf")])
@pytest.mark.parametrize("option", ["max_duration", "idle_timeout"])
def test_restore_duration_rejects_invalid_values(option, value):
    with pytest.raises(ValueError):
        Sandbox.restore("missing", name="restore-controls", **{option: value})


def test_fork_public_surface_and_outcome_alias():
    from microsandbox import BranchOutcome, ForkOutcome, SandboxHandle

    assert BranchOutcome is ForkOutcome
    for cls in (Sandbox, SandboxHandle):
        for method in ("fork", "fork_many", "branch", "branch_many"):
            assert callable(getattr(cls, method))
