import pytest


from render import Render


@pytest.fixture
def mock_values():
    return {
        "images": {
            "test_image": {
                "repository": "nginx",
                "tag": "latest",
            }
        },
    }


def test_empty_container_name(mock_values):
    render = Render(mock_values)
    with pytest.raises(Exception):
        render.add_container("  ", "test_image")


def test_resolve_image(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["image"] == "nginx:latest"


def test_missing_repo(mock_values):
    mock_values["images"]["test_image"]["repository"] = ""
    render = Render(mock_values)
    with pytest.raises(Exception):
        render.add_container("test_container", "test_image")


def test_missing_tag(mock_values):
    mock_values["images"]["test_image"]["tag"] = ""
    render = Render(mock_values)
    with pytest.raises(Exception):
        render.add_container("test_container", "test_image")


def test_non_existing_image(mock_values):
    render = Render(mock_values)
    with pytest.raises(Exception):
        render.add_container("test_container", "non_existing_image")


def test_pull_policy(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.set_pull_policy("always")
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["pull_policy"] == "always"


def test_invalid_pull_policy(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    with pytest.raises(Exception):
        c1.set_pull_policy("invalid_policy")


def test_clear_caps(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.add_caps(["NET_ADMIN"])
    c1.clear_caps()
    c1.healthcheck.disable()
    output = render.render()
    assert "cap_drop" not in output["services"]["test_container"]
    assert "cap_add" not in output["services"]["test_container"]


def test_privileged(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.set_privileged(True)
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["privileged"] is True


def test_tty(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.set_tty(True)
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["tty"] is True


def test_init(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.set_init(True)
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["init"] is True


def test_read_only(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.set_read_only(True)
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["read_only"] is True


def test_stdin(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.set_stdin(True)
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["stdin_open"] is True


def test_hostname(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.set_hostname("test_hostname")
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["hostname"] == "test_hostname"


def test_grace_period(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.set_grace_period(10)
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["stop_grace_period"] == "10s"


def test_user(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.set_user(1000, 1000)
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["user"] == "1000:1000"


def test_invalid_user(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.set_user(-100, 1000)


def test_add_group(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.add_group(1000)
    c1.add_group("video")
    output = render.render()
    assert output["services"]["test_container"]["group_add"] == [568, 1000, "video"]


def test_add_duplicate_group(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.add_group(1000)
    with pytest.raises(Exception):
        c1.add_group(1000)


def test_add_group_as_string(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.add_group("1000")


def test_add_docker_socket(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.add_docker_socket()
    output = render.render()
    assert output["services"]["test_container"]["group_add"] == [568, 999]
    assert output["services"]["test_container"]["volumes"] == [
        {
            "type": "bind",
            "source": "/var/run/docker.sock",
            "target": "/var/run/docker.sock",
            "read_only": True,
            "bind": {
                "propagation": "rprivate",
                "create_host_path": False,
            },
        }
    ]


def test_snd_device(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.add_snd_device()
    output = render.render()
    assert output["services"]["test_container"]["devices"] == ["/dev/snd:/dev/snd"]
    assert output["services"]["test_container"]["group_add"] == [29, 568]


def test_shm_size(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.set_shm_size_mb(10)
    output = render.render()
    assert output["services"]["test_container"]["shm_size"] == "10M"


def test_valid_caps(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.add_caps(["ALL", "NET_ADMIN"])
    output = render.render()
    assert output["services"]["test_container"]["cap_add"] == ["ALL", "NET_ADMIN"]
    assert output["services"]["test_container"]["cap_drop"] == ["ALL"]


def test_add_duplicate_caps(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.add_caps(["ALL", "NET_ADMIN", "NET_ADMIN"])


def test_invalid_caps(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.add_caps(["invalid_cap"])


def test_network_mode(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.set_network_mode("host")
    output = render.render()
    assert output["services"]["test_container"]["network_mode"] == "host"


def test_auto_network_mode_with_host_network(mock_values):
    mock_values["network"] = {"host_network": True}
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["network_mode"] == "host"


def test_network_mode_with_container(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.set_network_mode("service:test_container")
    output = render.render()
    assert output["services"]["test_container"]["network_mode"] == "service:test_container"


def test_network_mode_with_container_missing(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.set_network_mode("service:missing_container")


def test_invalid_network_mode(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.set_network_mode("invalid_mode")


def test_entrypoint(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.set_entrypoint(["/bin/bash", "-c", "echo hello $MY_ENV"])
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["entrypoint"] == ["/bin/bash", "-c", "echo hello $$MY_ENV"]


def test_command(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.set_command(["echo", "hello $MY_ENV"])
    c1.healthcheck.disable()
    output = render.render()
    assert output["services"]["test_container"]["command"] == ["echo", "hello $$MY_ENV"]


def test_add_ports(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.add_port({"port_number": 8081, "container_port": 8080, "bind_mode": "published"})
    c1.add_port({"port_number": 8082, "container_port": 8080, "bind_mode": "published", "protocol": "udp"})
    c1.add_port({"port_number": 8083, "container_port": 8080, "bind_mode": "exposed"})
    c1.add_port({"port_number": 8084, "container_port": 8080, "bind_mode": ""})
    c1.add_port(
        {"port_number": 9091, "container_port": 9091, "bind_mode": "published"},
        {"container_port": 9092, "protocol": "udp"},
    )
    output = render.render()
    assert output["services"]["test_container"]["ports"] == [
        {"published": 8081, "target": 8080, "protocol": "tcp", "mode": "ingress"},
        {"published": 8082, "target": 8080, "protocol": "udp", "mode": "ingress"},
        {"published": 9091, "target": 9092, "protocol": "udp", "mode": "ingress"},
    ]
    assert output["services"]["test_container"]["expose"] == ["8080/tcp"]


def test_add_ports_with_invalid_host_ips(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.add_port({"port_number": 8081, "container_port": 8080, "bind_mode": "published", "host_ips": "invalid"})


def test_add_ports_with_empty_host_ips(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.add_port({"port_number": 8081, "container_port": 8080, "bind_mode": "published", "host_ips": []})
    output = render.render()
    assert output["services"]["test_container"]["ports"] == [
        {"published": 8081, "target": 8080, "protocol": "tcp", "mode": "ingress"}
    ]


def test_set_ipc_mode(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.set_ipc_mode("host")
    output = render.render()
    assert output["services"]["test_container"]["ipc"] == "host"


def test_set_ipc_empty_mode(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.set_ipc_mode("")
    output = render.render()
    assert output["services"]["test_container"]["ipc"] == ""


def test_set_ipc_mode_with_invalid_ipc_mode(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.set_ipc_mode("invalid")


def test_set_ipc_mode_with_container_ipc_mode(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c2 = render.add_container("test_container2", "test_image")
    c2.healthcheck.disable()
    c1.set_ipc_mode("container:test_container2")
    output = render.render()
    assert output["services"]["test_container"]["ipc"] == "container:test_container2"


def test_set_ipc_mode_with_container_ipc_mode_and_invalid_container(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.set_ipc_mode("container:invalid")


def test_set_pid_mode(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.set_pid_mode("host")
    output = render.render()
    assert output["services"]["test_container"]["pid"] == "host"


def test_set_pid_empty_mode(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.set_pid_mode("")
    output = render.render()
    assert output["services"]["test_container"]["pid"] == ""


def test_set_pid_mode_with_invalid_pid_mode(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.set_pid_mode("invalid")


def test_set_pid_mode_with_container_pid_mode(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c2 = render.add_container("test_container2", "test_image")
    c2.healthcheck.disable()
    c1.set_pid_mode("container:test_container2")
    output = render.render()
    assert output["services"]["test_container"]["pid"] == "container:test_container2"


def test_set_pid_mode_with_container_pid_mode_and_invalid_container(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.set_pid_mode("container:invalid")


def test_set_cgroup(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    c1.set_cgroup("host")
    output = render.render()
    assert output["services"]["test_container"]["cgroup"] == "host"


def test_set_cgroup_invalid(mock_values):
    render = Render(mock_values)
    c1 = render.add_container("test_container", "test_image")
    c1.healthcheck.disable()
    with pytest.raises(Exception):
        c1.set_cgroup("invalid")
