import logging
import sys
import time
import uuid

import attr
from pytestshellutils.utils import ports
from saltfactories.daemons.container import SaltMinion

from tests.conftest import CODE_DIR

log = logging.getLogger(__name__)


@attr.s(kw_only=True, slots=True)
class SaltVirtMinionContainerFactory(SaltMinion):

    host_uuid = attr.ib(default=attr.Factory(uuid.uuid4))
    ssh_port = attr.ib(
        default=attr.Factory(ports.get_unused_localhost_port), repr=False
    )
    sshd_port = attr.ib(default=attr.Factory(ports.get_unused_localhost_port))
    libvirt_tcp_port = attr.ib(
        default=attr.Factory(ports.get_unused_localhost_port), repr=False
    )
    libvirt_tls_port = attr.ib(
        default=attr.Factory(ports.get_unused_localhost_port), repr=False
    )

    uri = attr.ib(init=False)
    ssh_uri = attr.ib(init=False)
    tcp_uri = attr.ib(init=False)
    tls_uri = attr.ib(init=False)

    def __attrs_post_init__(self):
        self.uri = "localhost:{}".format(self.sshd_port)
        self.ssh_uri = "qemu+ssh://{}/system".format(self.uri)
        self.tcp_uri = "qemu+tcp://localhost:{}/system".format(self.libvirt_tcp_port)
        self.tls_uri = "qemu+tls://127.0.0.1:{}/system".format(self.libvirt_tls_port)

        if "environment" not in self.container_run_kwargs:
            self.container_run_kwargs["environment"] = {}
        self.container_run_kwargs["environment"].update(
            {
                "SSH_PORT": str(self.ssh_port),
                "SSHD_PORT": str(self.sshd_port),
                "LIBVIRT_TCP_PORT": str(self.libvirt_tcp_port),
                "LIBVIRT_TLS_PORT": str(self.libvirt_tls_port),
                "NO_START_MINION": "1",
                "HOST_UUID": self.host_uuid,
                "PYTHONDONTWRITEBYTECODE": "1",
                "PYTHONPATH": str(CODE_DIR),
            }
        )
        super().__attrs_post_init__()
        if "volumes" not in self.container_run_kwargs:
            self.container_run_kwargs["volumes"] = {}
        self.container_run_kwargs["volumes"].update(
            {
                str(CODE_DIR): {"bind": "/salt", "mode": "z"},
            }
        )
        self.container_run_kwargs["working_dir"] = str(CODE_DIR)
        self.container_run_kwargs["network_mode"] = "host"
        self.container_run_kwargs["cap_add"] = ["ALL"]
        self.container_run_kwargs["privileged"] = True
        self.python_executable = "python3"
        self.container_start_check(self._check_script_path_exists)
        for port in (self.sshd_port, self.libvirt_tcp_port, self.libvirt_tls_port):
            self.check_ports[port] = port
        self.before_start(self._install_salt_in_container)

    def _check_script_path_exists(self, timeout_at):
        while time.time() <= timeout_at:
            # Once we're able to ls the salt-minion script it means the container
            # has salt installed
            ret = self.run("ls", "-lah", self.get_script_path())
            if ret.returncode == 0:
                break
            time.sleep(1)
        else:
            return False
        return True

    def _install_salt_in_container(self):
        ret = self.run("bash", "-c", "echo $SALT_PY_VERSION")
        assert ret.returncode == 0
        if not ret.stdout:
            log.warning(
                "The 'SALT_PY_VERSION' environment variable is not set on the container"
            )
            salt_py_version = 3
            ret = self.run(
                "python3",
                "-c",
                "import sys; sys.stderr.write('{}.{}'.format(*sys.version_info))",
            )
            assert ret.returncode == 0
            if not ret.stdout:
                requirements_py_version = "{}.{}".format(*sys.version_info)
            else:
                requirements_py_version = ret.stdout.strip()
        else:
            salt_py_version = requirements_py_version = ret.stdout.strip()

        self.python_executable = f"python{salt_py_version}"

        ret = self.run(
            self.python_executable,
            "-m",
            "pip",
            "install",
            f"--constraint=/salt/requirements/static/ci/py{requirements_py_version}/linux.txt",
            "/salt",
        )
        log.debug("Install Salt in the container: %s", ret)
        assert ret.returncode == 0
