import subprocess
import os
from stimela import utils
import stimela
import hashlib
from shutil import which


version = None

for item in ["apptainer", "singularity"]:
    BINARY = which(item)
    BINARY_NAME = item
    if BINARY:
        __version_string = subprocess.check_output([BINARY, "--version"]).decode("utf8")
        version = __version_string.strip().split()[-1]
        if BINARY_NAME == "singularity" and version < "3.0.0":
            suffix = ".img"
        else:
            suffix = ".sif"
        break


class SingularityError(Exception):
    pass


def pull(image, name, docker=True, directory=".", force=False):
    """
    pull an image
    """
    if docker:
        fp = "docker://{0:s}".format(image)
    else:
        fp = image
    if not os.path.exists(directory):
        os.mkdir(directory)

    image_path = os.path.abspath(os.path.join(directory, name))
    if os.path.exists(image_path) and not force:
        stimela.logger().info(
            f"Singularity image already exists at '{image_path}'. To replace it, please re-run with the 'force' option"
        )
    else:
        utils.xrun(
            f"cd {directory} && {BINARY}",
            ["pull", "--force" if force else "", "--name", name, fp],
        )
    return 0


class Container(object):
    def __init__(
        self,
        image,
        name,
        volumes=None,
        logger=None,
        time_out=-1,
        runscript="/singularity",
        environs=None,
        workdir=None,
        execdir=".",
    ):
        """
        Python wrapper to singularity tools for managing containers.
        """

        self.image = image
        self.volumes = volumes or []
        self.environs = environs or []
        self.logger = logger
        self.status = None
        self.WORKDIR = workdir
        self.RUNSCRIPT = runscript
        self.PID = os.getpid()
        self.uptime = "00:00:00"
        self.time_out = time_out
        self.execdir = execdir

        self._env = os.environ.copy()

        hashname = hashlib.md5(name.encode("utf-8")).hexdigest()[:3]
        self.name = hashname if version < "3.0.0" else name

    def add_volume(self, host, container, perm="rw", noverify=False):
        if os.path.exists(host) or noverify:
            if self.logger:
                self.logger.debug(
                    "Mounting volume [{0}] in container [{1}] at [{2}]".format(
                        host, self.name, container
                    )
                )
            host = os.path.abspath(host)
        else:
            raise IOError(
                "Path {0} cannot be mounted on container: File doesn't exist".format(
                    host
                )
            )

        self.volumes.append(":".join([host, container, perm]))

        return 0

    def add_environ(self, key, value):
        self.logger.debug(
            "Adding environ varaible [{0}={1}] in container {2}".format(
                key, value, self.name
            )
        )
        self.environs.append("=".join([key, value]))
        key_ = f"{BINARY_NAME.upper()}ENV_{key}"

        self.logger.debug(
            f"Setting singularity environmental variable {key_}={value} on host"
        )
        self._env[key_] = value

        return 0

    def run(self, *args, output_wrangler=None):
        """
        Run a singularity container instance
        """

        if self.volumes:
            volumes = " --bind " + " --bind ".join(self.volumes)
        else:
            volumes = ""

        if not os.path.exists(self.image):
            self.logger.error(
                f"The image, {self.image}, required to run this cab does not exist."
                " Please run 'stimela pull --help' for help on how to download the image"
            )
            raise SystemExit from None

        self.status = "running"
        self._print(
            "Starting container [{0:s}]. Timeout set to {1:d}. The container ID is printed below.".format(
                self.name, self.time_out
            )
        )
        utils.xrun(
            f"{BINARY} run --workdir {self.execdir} --contain",
            list(args) + [volumes, self.image, self.RUNSCRIPT],
            log=self.logger,
            timeout=self.time_out,
            output_wrangler=output_wrangler,
            env=self._env,
            logfile=self.logfile,
        )

        self.status = "exited"

        return 0

    def _print(self, message):
        if self.logger:
            self.logger.info(message)
        else:
            print(message)

        return 0
