from unittest import mock

import pytest

from .. import Argument, Layer, Node


@mock.patch("shutil.which", side_effect=lambda x: f"/path/to/{x}")
def test_validate_mismatched_outputs(mock_which):
    layer = Layer("my_exec")
    layer += Node(
        arguments=Argument("idx", 0),
        outputs=Argument("out", "a.txt"),
    )
    layer += Node(
        arguments=Argument("idx", 1),
        outputs=Argument("out-different", "b.txt"),
    )
    # need a third node so nodes[:-1] includes the mismatched one
    layer += Node(
        arguments=Argument("idx", 2),
        outputs=Argument("out", "c.txt"),
    )
    with pytest.raises(AssertionError):
        layer.validate()


@mock.patch("shutil.which", side_effect=lambda x: f"/path/to/{x}")
def test_validate_protected_variable_name(mock_which):
    layer = Layer("my_exec")
    layer += Node(variables={"input": "bad"})
    with pytest.raises(ValueError, match="protected condor name"):
        layer.validate()


@mock.patch("shutil.which", side_effect=lambda x: f"/path/to/{x}")
def test_command_no_transfer(mock_which):
    layer = Layer("my_exec", transfer_files=False)
    node = Node(
        inputs=Argument("data", "/abs/path/file.txt"),
        outputs=Argument("out", "result.txt"),
    )
    layer += node
    cmd = layer.command(node)
    # without transfer, absolute paths are preserved
    assert "/abs/path/file.txt" in cmd


@mock.patch("shutil.which", side_effect=lambda x: f"/path/to/{x}")
def test_command_no_readjust_paths(mock_which):
    layer = Layer("my_exec", transfer_files=True)
    node = Node(
        inputs=Argument("data", "/abs/path/file.txt"),
        outputs=Argument("out", "sub/result.txt"),
    )
    layer += node
    cmd = layer.command(node, readjust_paths=False)
    # with readjust_paths=False, paths are not basified
    assert "/abs/path/file.txt" in cmd
    assert "sub/result.txt" in cmd


@mock.patch("shutil.which", return_value=None)
@mock.patch("os.path.exists", return_value=False)
def test_executable_not_found_warns(mock_exists, mock_which):
    layer = Layer("nonexistent_exec")
    layer += Node(arguments=Argument("x", 1))
    with pytest.warns(UserWarning, match="not found"):
        layer.config()


@mock.patch("os.path.exists", return_value=True)
def test_executable_found_by_path(mock_exists):
    layer = Layer("./local_exec")
    layer += Node(arguments=Argument("x", 1))
    config = layer.config()
    assert config["submit_description"]["executable"] == "./local_exec"


@mock.patch("shutil.which", side_effect=lambda x: f"/path/to/{x}")
def test_layer_new(mock_which):
    """Layer.new() copies config but not nodes."""
    layer = Layer(
        "my_exec",
        name="custom",
        universe="docker",
        log_dir="my_logs",
        retries=5,
        transfer_files=False,
        submit_description={"request_cpus": 4},
    )
    layer += Node(arguments=Argument("x", 1))

    clone = layer.new()

    assert clone.executable == layer.executable
    assert clone.name == layer.name
    assert clone.universe == layer.universe
    assert clone.log_dir == layer.log_dir
    assert clone.retries == layer.retries
    assert clone.transfer_files == layer.transfer_files
    assert clone.submit_description == layer.submit_description
    assert clone.nodes == []


def test_bare_string_inputs_outputs():
    """A bare string as input/output should be wrapped, not iterated."""
    node = Node(inputs="data.txt", outputs="result.txt")
    assert len(node.inputs) == 1
    assert node.inputs[0].name == "data.txt"
    assert len(node.outputs) == 1
    assert node.outputs[0].name == "result.txt"


@mock.patch("shutil.which", side_effect=lambda x: f"/path/to/{x}")
def test_static_literal_inputs_outputs(mock_which):
    """Bare strings as inputs/outputs become static Literals."""
    layer = Layer("my_exec")
    layer += Node(
        arguments=Argument("idx", 0),
        inputs=["data.txt"],
        outputs=["/path/to/result.txt"],
    )
    config = layer.config()
    submit = config["submit_description"]

    # static inputs appear directly in arguments (not as condor variables)
    assert "data.txt" in submit["arguments"]
    # static outputs get basified when transfer is on
    assert "result.txt" in submit["arguments"]

    # file transfer lists include the static files directly
    assert "data.txt" in submit["transfer_input_files"]
    assert "result.txt" in submit["transfer_output_files"]

    # absolute path produces a remap
    assert "result.txt=/path/to/result.txt" in submit["transfer_output_remaps"]
