import subprocess
import pytest

from tuxmake.build import Build
from tuxmake.exceptions import InvalidRuntimeError
from tuxmake.exceptions import RuntimePreparationFailed
from tuxmake.runtime import get_runtime
from tuxmake.runtime import Runtime
from tuxmake.runtime import NullRuntime
from tuxmake.runtime import DockerRuntime
from tuxmake.runtime import DockerLocalRuntime
from tuxmake.wrapper import Wrapper


@pytest.fixture
def build(linux):
    b = Build(linux)
    return b


class TestGetRuntime:
    def test_null_runtime(self):
        assert isinstance(get_runtime(None), NullRuntime)

    def test_docker_runtime(self):
        assert isinstance(get_runtime("docker"), DockerRuntime)

    def test_invalid_runtime(self):
        with pytest.raises(InvalidRuntimeError):
            get_runtime("invalid")
        with pytest.raises(InvalidRuntimeError):
            get_runtime("xyz")


class TestNullRuntime:
    def test_get_command_line(self, build):
        assert NullRuntime().get_command_line(build, ["date"], False) == ["date"]

    def test_prepare_warns_about_versioned_toolchain(self, build, mocker):
        build.toolchain.version_suffix = "-10"
        log = mocker.patch("tuxmake.build.Build.log")
        runtime = NullRuntime()
        runtime.prepare(build)
        log.assert_called()


@pytest.fixture
def get_docker_image(mocker):
    return mocker.patch("tuxmake.toolchain.Toolchain.get_docker_image")


class TestDockerRuntime:
    def test_docker_image(self, build, get_docker_image):
        get_docker_image.return_value = "foobarbaz"
        runtime = DockerRuntime()
        assert runtime.get_image(build) == "foobarbaz"

    def test_override_docker_image(self, build, monkeypatch):
        monkeypatch.setenv("TUXMAKE_DOCKER_IMAGE", "foobar")
        runtime = DockerRuntime()
        assert runtime.get_image(build) == "foobar"

    def test_prepare(self, build, get_docker_image, mocker):
        get_docker_image.return_value = "myimage"
        check_call = mocker.patch("subprocess.check_call")
        DockerRuntime().prepare(build)
        check_call.assert_called_with(["docker", "pull", "myimage"])

    def test_get_command_line(self, build):
        cmd = DockerRuntime().get_command_line(build, ["date"], False)
        assert cmd[0:2] == ["docker", "run"]
        assert cmd[-1] == "date"

    def test_environment(self, build):
        build.environment = {"FOO": "BAR"}
        cmd = DockerRuntime().get_command_line(build, ["date"], False)
        assert "--env=FOO=BAR" in cmd

    def test_ccache(self, build, home):
        ccache = Wrapper("ccache")
        orig_ccache_dir = ccache.environment["CCACHE_DIR"]
        build.wrapper = ccache
        cmd = DockerRuntime().get_command_line(build, ["date"], False)
        assert "--env=CCACHE_DIR=/ccache-dir" in cmd
        assert f"--volume={orig_ccache_dir}:/ccache-dir" in cmd

    def test_sccache_with_path(self, build, home):
        sccache_from_host = Wrapper("/opt/bin/sccache")
        build.wrapper = sccache_from_host
        cmd = DockerRuntime().get_command_line(build, ["date"], False)
        assert "--volume=/opt/bin/sccache:/usr/local/bin/sccache" in cmd

    def test_TUXMAKE_DOCKER_RUN(self, build, monkeypatch):
        monkeypatch.setenv(
            "TUXMAKE_DOCKER_RUN", "--hostname=foobar --env=FOO='bar baz'"
        )
        cmd = DockerRuntime().get_command_line(build, ["bash"], False)
        assert "--hostname=foobar" in cmd
        assert "--env=FOO=bar baz" in cmd

    def test_interactive(self, build):
        cmd = DockerRuntime().get_command_line(build, ["bash"], True)
        assert "--interactive" in cmd
        assert "--tty" in cmd

    def test_bases(self):
        assert "base-debian" in [t.name for t in DockerRuntime().base_images]

    def test_ci_images(self):
        assert "ci-python3.8" in [t.name for t in DockerRuntime().ci_images]

    def test_toolchains(self):
        assert "gcc" in [t.name for t in DockerRuntime().toolchain_images]

    def test_listed_as_supported(self):
        assert "docker" in Runtime.supported()


class TestDockerLocalRuntime:
    def test_prepare_checks_local_image(self, build, get_docker_image, mocker):
        get_docker_image.return_value = "mylocalimage"
        check_call = mocker.patch("subprocess.check_call")
        runtime = DockerLocalRuntime()

        runtime.prepare(build)
        check_call.assert_called_with(
            ["docker", "image", "inspect", "mylocalimage"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )

    def test_prepare_image_not_found(self, build, get_docker_image, mocker):
        get_docker_image.return_value = "foobar"
        mocker.patch(
            "subprocess.check_call",
            side_effect=subprocess.CalledProcessError(
                1, ["foo"], stderr="Image not found"
            ),
        )
        with pytest.raises(RuntimePreparationFailed) as exc:
            DockerLocalRuntime().prepare(build)
        assert "image foobar not found locally" in str(exc)

    def test_listed_as_supported(self):
        assert "docker-local" in Runtime.supported()
