"""Mutation-safe contracts for remote input and shell construction."""

from __future__ import annotations

import shlex

import pytest

from obliteratus.remote_contracts import (
    normalize_gpu_selection,
    parse_remote_target,
    remote_python_command,
    remote_scp_spec,
    validate_remote_settings,
)


VALID_REMOTE_SETTINGS = {
    "host": "compute.example",
    "user": "runner",
    "port": 22,
    "remote_dir": "/tmp/obliteratus",
    "python": "python3",
    "gpus": None,
    "install_source": "obliteratus",
}


@pytest.mark.parametrize(
    ("raw", "expected"),
    [(None, None), ("all", "all"), ("ALL", "all"), ("0", "0"), (" 02, 0,11 ", "2,0,11")],
)
def test_gpu_selection_normalizes_public_values(raw, expected):
    assert normalize_gpu_selection(raw) == expected


@pytest.mark.parametrize(
    ("raw", "message"),
    [
        ("", "remote gpus must be a non-empty string"),
        ("\x7f", "remote gpus may not contain control characters"),
        ("0,,1", "remote gpus must be 'all' or comma-separated non-negative integers"),
        ("-1", "remote gpus must be 'all' or comma-separated non-negative integers"),
        (
            "0; touch /tmp/pwned",
            "remote gpus must be 'all' or comma-separated non-negative integers",
        ),
        ("gpu0", "remote gpus must be 'all' or comma-separated non-negative integers"),
        ("0\n1", "remote gpus may not contain control characters"),
    ],
)
def test_gpu_selection_rejects_non_device_input(raw, message):
    with pytest.raises(ValueError) as error:
        normalize_gpu_selection(raw)
    assert str(error.value) == message


def test_remote_target_defaults_user_and_parses_single_at_sign():
    assert parse_remote_target("compute.example") == ("root", "compute.example")
    assert parse_remote_target("runner@compute.example") == ("runner", "compute.example")
    assert parse_remote_target(" runner@compute.example ") == ("runner", "compute.example")


@pytest.mark.parametrize(
    ("target", "message"),
    [
        ("", "remote target must be a non-empty string"),
        ("\x7f", "remote target may not contain control characters"),
        ("runner@", "remote host must be a non-empty string"),
        ("@host", "remote user must be a non-empty string"),
        ("bad user@host", "remote user contains unsupported characters"),
        (
            "runner@-oProxyCommand=evil",
            "remote host must be a host name or address without user or options",
        ),
        ("runner@host name", "remote host must be a host name or address without user or options"),
        ("a@b@host", "remote target may contain at most one user separator"),
    ],
)
def test_remote_target_rejects_empty_or_option_like_components(target, message):
    with pytest.raises(ValueError) as error:
        parse_remote_target(target)
    assert str(error.value) == message


@pytest.mark.parametrize(
    ("override", "message"),
    [
        ({"host": ""}, "remote host must be a non-empty string"),
        (
            {"host": "bad@host"},
            "remote host must be a host name or address without user or options",
        ),
        (
            {"host": "bad host"},
            "remote host must be a host name or address without user or options",
        ),
        ({"user": ""}, "remote user must be a non-empty string"),
        ({"user": "-oProxy"}, "remote user contains unsupported characters"),
        ({"user": "bad user"}, "remote user contains unsupported characters"),
        ({"port": 0}, "remote port must be an integer from 1 through 65535"),
        ({"port": 65536}, "remote port must be an integer from 1 through 65535"),
        ({"port": True}, "remote port must be an integer from 1 through 65535"),
        ({"port": "22"}, "remote port must be an integer from 1 through 65535"),
        ({"remote_dir": ""}, "remote directory must be a non-empty string"),
        ({"remote_dir": "relative"}, "remote directory must be an absolute POSIX path"),
        ({"python": ""}, "remote Python must be a non-empty string"),
        ({"install_source": ""}, "remote install source must be a non-empty string"),
        (
            {"install_source": "bad\nsource"},
            "remote install source may not contain control characters",
        ),
    ],
)
def test_remote_settings_reject_invalid_boundaries(override, message):
    values = dict(VALID_REMOTE_SETTINGS)
    values.update(override)
    with pytest.raises(ValueError) as error:
        validate_remote_settings(**values)
    assert str(error.value) == message


@pytest.mark.parametrize("port", [1, 65535])
def test_remote_settings_accept_port_boundaries(port):
    values = dict(VALID_REMOTE_SETTINGS, port=port, gpus=" 02,0 ")
    assert validate_remote_settings(**values) == "2,0"


def test_remote_python_command_preserves_each_untrusted_value_as_one_argument():
    command = remote_python_command(
        "/opt/python builds/current/python",
        ["-m", "obliteratus", "run", "/tmp/a config.yml", "--preset", "x; echo injected"],
        gpus="02,0",
    )
    assert shlex.split(command) == [
        "env",
        "CUDA_VISIBLE_DEVICES=2,0",
        "/opt/python builds/current/python",
        "-m",
        "obliteratus",
        "run",
        "/tmp/a config.yml",
        "--preset",
        "x; echo injected",
    ]


def test_remote_python_command_omits_environment_for_all_devices():
    assert shlex.split(remote_python_command("python3", ["-V"], gpus="all")) == ["python3", "-V"]
    assert shlex.split(remote_python_command("python3", ["-V"])) == ["python3", "-V"]


def test_remote_python_command_reports_invalid_python_contract():
    with pytest.raises(ValueError) as error:
        remote_python_command("", ["-V"])
    assert str(error.value) == "remote Python must be a non-empty string"


def test_remote_scp_spec_quotes_remote_paths_and_directory_suffix():
    assert remote_scp_spec("runner@host", "/tmp/result file", directory=True) == (
        "runner@host:'/tmp/result file/'"
    )
    assert remote_scp_spec("runner@host", "/tmp/results/", directory=True) == (
        "runner@host:/tmp/results/"
    )
    assert remote_scp_spec("runner@host", "/tmp/config.yml") == "runner@host:/tmp/config.yml"


@pytest.mark.parametrize(
    ("target", "path", "message"),
    [
        ("", "/tmp/config.yml", "SSH target must be a non-empty string"),
        ("runner@host", "", "remote SCP path must be a non-empty string"),
        ("runner@host", "/tmp/bad\x7fpath", "remote SCP path may not contain control characters"),
    ],
)
def test_remote_scp_spec_reports_invalid_input_contract(target, path, message):
    with pytest.raises(ValueError) as error:
        remote_scp_spec(target, path)
    assert str(error.value) == message
