from common.utils import  PathManager, Overlay, HostMessage
from common.env import Environment
from common.dirs import ShareDirs, BaseDirs
import subprocess
import os
import pwd
from common.errors import KareErrors
import random

class Container(PathManager, Overlay, HostMessage):

    def __init__(self, shadow=False,log=None, chroot=False,isInstall=False):
        child_container = {
            "docker": Docker(shadow, log),
            "crun": Crun(shadow, log),
            "bwrap": Bwrap(shadow, log),
            "chroot": Chroot(shadow, log)
        }
        self.shadow = shadow
        self.log = log
        if chroot: 
            self.container = child_container.get("chroot")
            self.log.info("当前生效容器类型: chroot")
            if isInstall:
                PathManager.PACKAGE_SAVE_PATH="/kare-applications/"
                PathManager.VP_DEBIAN_PATH="/kare-applications/vpws/vp/DEBIAN"   
        else:
            self.container = child_container.get(self.container_type)
            self.log.info("当前生效容器类型: {}".format(self.container_type))

    """
    :brief 容器创建，包括overlay目录创建和挂载
    :param release: 基础环境信息
    :param package: 需要安装的包
    """   
    def create_container(self, release, package):
        package_path = self.PACKAGE_SAVE_PATH + package
        self.log.info("package_path: {}".format(package_path))
        # base_dirs 基础环境存储目录
        base_dirs = self.get_base_dirs(release)
        self.log.info("base_dirs: {}".format(base_dirs))
        self.create_overlay_path(base_dirs, package_path)
        self.log.info("完成overlay目录创建")
        self.mount_overlay(base_dirs, package_path)
        self.log.info("完成overlay目录挂载")

    """
    :brief 获取贡献目录列表
    :return: 贡献目录列表
    """
    @property
    def get_share_dirs(self):
        return ShareDirs(shadow=self.shadow).share_dirs
    
    """
    :brief 获取应用目录列表
    :return: 应用目录列表
    """
    def get_base_dirs(self, release_path):
        return BaseDirs(release_path).base_dirs
    
    def get_environment(self, release):
        return Environment(release).envs

    """
    :brief检测指定的 deb 包是否已安装
    :param package_name: 要检查的包名
    :return: True 表示已安装，False 表示未安装
    """
    def container_installed(self, container):
        try:
            output = subprocess.check_output(
                ['dpkg-query', '-W', '-f=${Status}', container],
                stderr=subprocess.STDOUT,
                universal_newlines=True
            )
            return 'install ok installed' in output
        except subprocess.CalledProcessError:
            return False

    """
    :brief获取当前用户的 UID 
    :return: UID 
    """
    def get_current_uid(self):
        uid = os.getuid()
        return uid

    """
    :brief获取当前用户的 GID 
    :return:  GID 
    """
    def get_current_gid(self):
        uid = os.getuid()
        user_info = pwd.getpwuid(uid)
        return user_info.pw_gid

class Docker(Container):
    def __init__(self, shadow, log):
        pass

    def create_container(self):
        super().create_container()

    def start_container(self):
        pass

    def debug_container(self):
        ...

    def exec(self):
        pass

class Crun(Container):
    def __init__(self, shadow, log):
        self.shadow = shadow
        self.log = log
        if self.container_installed("crun") is False:
            self.log.error(KareErrors.CONTAINER_NOT_INSTALL[1])
            raise RuntimeError(KareErrors.CONTAINER_NOT_INSTALL)
        self.white_list = self.get_config_json(self.WHITELIST_RUN_FILE) 
        self.cmd = "/usr/bin/crun run --bundle {}"
        self.domainname = "kare"
        self.hostname = "kare"
        self.capabilities = [
            "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_DAC_READ_SEARCH", "CAP_FSETID", "CAP_FOWNER",
            "CAP_MKNOD", "CAP_NET_RAW", "CAP_SETGID", "CAP_SETUID",
            "CAP_SETFCAP", "CAP_SETPCAP", "CAP_NET_BIND_SERVICE", "CAP_SYS_CHROOT",
            "CAP_SYS_ADMIN", "CAP_KILL", "CAP_SYS_RAWIO"]

        # self.process_capabilities = [
        #     "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_DAC_READ_SEARCH", "CAP_FOWNER", "CAP_FSETID", "CAP_KILL", "CAP_SETGID", "CAP_SETUID", "CAP_SETPCAP", "CAP_LINUX_IMMUTABLE", "CAP_NET_BIND_SERVICE", "CAP_NET_BROADCAST", "CAP_NET_ADMIN", "CAP_NET_RAW", "CAP_IPC_LOCK", "CAP_IPC_OWNER", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_CHROOT", "CAP_SYS_PTRACE", "CAP_SYS_PACCT", "CAP_SYS_ADMIN", "CAP_SYS_BOOT", "CAP_SYS_NICE", "CAP_SYS_RESOURCE", "CAP_SYS_TIME", "CAP_SYS_TTY_CONFIG", "CAP_MKNOD", "CAP_LEASE", "CAP_AUDIT_WRITE", "CAP_AUDIT_CONTROL", "CAP_SETFCAP", "CAP_MAC_OVERRIDE", "CAP_MAC_ADMIN", "CAP_SYSLOG", "CAP_WAKE_ALARM", "CAP_BLOCK_SUSPEND", "CAP_AUDIT_READ"]
        
        self.cgroupsPath = "kare{}".format(random.randint(1, 10000))
        # self.cgroupsPath = ""
        self.mounts = []
        self.ociVersion = "1.0.1"
        self.linux = {}
        self.process = {}

    def create_container(self, base_env, package):
        super().create_container(base_env, package)

    def start_container(self):
        pass

    def debug_container(self):
        ...
    
    """
    :brief 配置crun配置文件
    :param package_path: 包存储路径/opt/kare-applications/xxx/merge
    :param crun_cmd: 运行参数
    :return cmd: crun运行命令
    """ 
    def exec(self, package_path, crun_cmd, release=None, crun_container_name=None, install=False, white_container_name=None, crun_config=None):
        # if self.base_dirs is None:
        #     self.base_dirs = self.get_base_dirs(package_path)
        self.build_config_json(package_path, crun_cmd, release, install=install, white_container_name=white_container_name, crun_config=crun_config)
        if crun_container_name is None:
            cmd = " ".join([self.cmd.format(package_path), "crun{}".format(random.randint(1, 10000))])
            if crun_config is not None:
                cmd = " ".join([self.cmd.format(package_path), "-f", "/tmp/{}".format(crun_config), "--preserve-fds=4", "crun{}".format(random.randint(1, 10000))])
        else:
            cmd = " ".join([self.cmd.format(package_path), crun_container_name])
            if crun_config is not None:
                cmd = " ".join([self.cmd.format(package_path), "-f", "/tmp/{}".format(crun_config), "--preserve-fds=4", crun_container_name])
        self.log.info("crun exec : {}".format(cmd))
        return cmd

    @property
    def build_gid_mappings(self):
        return {
            "containerID": self.get_current_gid(),
            "hostID": self.get_current_gid(),
            "size": 65536 if self.get_current_uid() == 0 else 1
        }

    @property
    def build_uid_mappings(self):
        return {
            "containerID": self.get_current_uid(),
            "hostID": self.get_current_uid(),
            "size": 65536 if self.get_current_uid() == 0 else 1
        }

    """
    :brief 获取crun配置文件中namespaces参数
    :return:  crun namespaces参数 
    """    
    def build_namespaces(self, install=False):
        # namespaces = [{"type": "pid"}, {"type": "mount"}, {"type": "uts"}, {"type": "user" }]
        namespaces = [{"type": "mount"}, {"type": "uts"}]
        # if self.get_current_uid() == 0:
        #     namespaces.append({"type": "user" })
        if install:
            namespaces.append({"type": "pid" })
        return namespaces

    """
    :brief 配置crun配置文件中linux参数
    """    
    def build_linux(self, install=False):
        self.linux.setdefault("capabilities", self.capabilities) 
        self.linux.setdefault("rootfsPropagation","shared") 
        self.log.info("crun capabilities : {}".format(self.capabilities)) 
        # self.linux.setdefault("cgroupsPath", self.cgroupsPath)
        # self.log.info("crun cgroupsPath : {}".format(self.cgroupsPath)) 
        self.linux.setdefault("gidMappings", [self.build_gid_mappings]) 
        self.log.info("crun gidMappings : {}".format([self.build_gid_mappings])) 
        self.linux.setdefault("uidMappings", [self.build_uid_mappings])
        self.log.info("crun uidMappings : {}".format([self.build_uid_mappings])) 
        self.linux.setdefault("namespaces", self.build_namespaces(install=install))
        self.log.info("crun namespaces : {}".format(self.build_namespaces(install=install))) 

    """
    :brief 配置crun配置文件mount参数中，base相关目录
    :param package_path: 包存储路径/opt/kare-applications/xxx/merge
    """ 
    def build_base_dirs(self, package_path):
        for path in self.get_base_dirs(package_path):
            basepath ="/" + os.path.basename(path)
            self.mounts.append({
                "destination": basepath,
                "options": ["rw", "rbind", "rprivate"],
                "source": path,
                "type": "bind"
            })

    """
    :brief 配置crun配置文件中mount参数
    :param package_path: 包存储路径/opt/kare-applications/xxx/merge
    """    
    def build_mounts(self, package_path):
        self.build_base_dirs(package_path)
        # self.mounts += ShareDirs().get_tmpfs_dirs
        # self.mounts += ShareDirs().get_proc_dirs
        # self.mounts += ShareDirs().get_cgroup_dirs
        self.mounts += ShareDirs(self.shadow).get_bind_dirs

    """
    :brief 配置crun配置文件中process参数
    :param crun_cmd: 运行参数
    """    
    def build_process(self, crun_cmd, release, install=False, white_container_name=None, cups_filter=False):
        self.process['args'] = ["/bin/bash", "-c", crun_cmd]
        self.log.info("crun args : {}".format(self.process["args"]))
        self.process.setdefault("capabilities", {
            "ambient" :self.capabilities,
            "bounding" :self.capabilities,
            "effective" :self.capabilities,
            # "inheritable" :self.capabilities,
            "permitted" :self.capabilities
        })
        self.process.setdefault("cwd", os.environ.get("HOME") if os.environ.get("HOME") else "/root")
        self.log.info("crun cwd : {}".format(self.process["cwd"]))
        self.process.setdefault("env", self.get_crun_environment(release))
        self.log.info("crun env : {}".format(self.process["env"]))
        self.process.setdefault("user", {
            "uid": self.get_current_uid(),
            "gid": self.get_current_gid()
        })
        self.log.info("crun user : {}".format(self.process["user"]))
        self.process.setdefault("noNewPrivileges", False)
        self.process.setdefault("terminal", False if white_container_name in self.white_list else True)
        if self.get_current_uid() == 0:
            self.process["terminal"] = True
        if cups_filter:
            self.process["terminal"] = False

    """
    :brief 编写crun配置文件
    :param package_path: 包存储路径/opt/kare-applications/xxx/merge
    :param crun_cmd: 运行参数
    """
    def build_config_json(self, package_path, crun_cmd, release, install=False, white_container_name=None, crun_config=None):
        conf = package_path + "/config.json"
        if crun_config is not None:
            conf = "/tmp/" + crun_config
        self.set_config_json(conf, "domainname", self.domainname)
        if white_container_name and white_container_name == "v10sp1Driver":
            self.set_config_json(conf, "hostname", os.uname().nodename)
        else:
            self.set_config_json(conf, "hostname", self.hostname)
        self.build_linux(install=install)
        self.set_config_json(conf, "linux", self.linux)
        self.build_mounts(package_path)
        self.set_config_json(conf, "mounts", self.mounts)
        self.log.info("crun mounts : {}".format(self.mounts))
        self.set_config_json(conf, "ociVersion", self.ociVersion)
        self.build_process(crun_cmd, release, install=install, white_container_name=white_container_name, cups_filter=False if crun_config is None else True)
        self.process["env"].append("KARE_PACKAGE_PATH={}".format(package_path))
        self.set_config_json(conf, "process", self.process)
        self.set_config_json(conf, "root", {"path": package_path,"readonly": False})
    
    """
    :brief 获取crun配置文件中env参数
    :return:  crun环境变量 
    """
    def get_crun_environment(self, release):
        return ["{}={}".format(env, value) for env, value in self.get_environment(release).items()]


class Bwrap(Container):
    def __init__(self, shadow, log):
        return
        self.shadow = shadow
        if self.container_installed("bubblewrap") is False:
            raise Exception("bubblewrap not installed")
        self.cmd = "/usr/bin/bwrap"
        self.user = "--uid {} --gid {}".format(self.get_current_uid(), self.get_current_gid())
        self.share_path = self.build_share_dirs
        self.env = self.build_environment
        self.base_dirs = None
        self.log = log

    def create_container(self,base_env, package):
        super().create_container(base_env, package)

    def start_container(self):
        pass

    def debug_container(self):
        ...

    """
    :brief 容器内执行命令
    :param package_path: 包存储路径
    :param cmd: 容器内所需命令行
    """
    def exec(self, package_path, bwrap_cmd):
        if self.base_dirs is None:
            self.base_dirs = self.build_base_dirs(package_path)
        return " ".join([self.cmd, self.user, self.base_dirs, "--dev-bind /dev /dev", self.share_path, self.env, bwrap_cmd])
    

    """
    :brief构建bwrap命令中--bind参数
    :param package_path: 包存储路径
    :return:  包文件夹 
    """
    def build_base_dirs(self, package_path):
        return ' '.join(['--bind {} /{}'.format(path, os.path.basename(path)) for path in self.get_base_dirs(package_path)])

    """
    :brief构建bwrap命令中--bind参数
    :return:  贡献文件夹 
    """
    @property
    def build_share_dirs(self):
        return ' '.join(['--bind {} {}'.format(path, path) for path in self.get_share_dirs])
    
    """
    :brief构建bwrap命令中--setenv参数
    :return:  bwrap环境变量 
    """
    @property
    def build_environment(self):
        return ' '.join(['--setenv {} {}'.format(env, value) for env, value in self.get_environment.items()])


class Chroot(Container):
    def __init__(self, shadow, log):
        self.cmd = "/usr/sbin/chroot"
        self.log = log
        self.shadow = shadow
        self.package=""
        self.envs = ["KARE_APP=True"]

    """
    :brief 运行chroot环境需要执行的命令行
    :param package_path: 包存储路径
    :param cmd: 容器内所需命令行
    """
    def exec(self, package_path, chroot_cmd):
        env_cmd = 'env {}'.format(" ".join(self.envs))
        return  " ".join([self.cmd, package_path, env_cmd, chroot_cmd])
    
    """
    :brief 构建chroot环境，挂载共享的目录
    :param base_env: 包存储路径
    :param package: 包名
    """
    def create_container(self, base_env, package):
        super().create_container(base_env, package)
        self.package = package
        self.mount_share_dirs(package)

    def start_container(self):
        pass
    
    """
    :brief 获取通过chroot安装应用时候需要共享的目录列表
    """
    @property
    def get_chroot_share_dirs(self):
        return ShareDirs(shadow=self.shadow).get_chroot_share_dir
    
    """
    :brief 获取通过chroot安装应用时候需要共享的目录列表
    """
    @property
    def get_chroot_dkms_dir(self):
        return ShareDirs(shadow=self.shadow).get_chroot_dkms_dir
    
    """
    :brief 挂载通过chroot安装应用时候需要共享的目录
    """
    def mount_share_dirs(self, package):
        for dir in self.get_chroot_share_dirs if  not package == "dkmsContainer" else self.get_chroot_share_dirs + self.get_chroot_dkms_dir:
            if os.path.isdir:
                full_path = os.path.abspath(self.PACKAGE_SAVE_PATH + package + "/merge" + dir)
                if not os.path.exists(full_path):
                    os.makedirs(full_path)
                mount_pint = full_path
            elif os.path.isfile:
                file_path = os.path.abspath(dir)
                full_path = os.path.abspath(self.PACKAGE_SAVE_PATH + package + "/merge" +  file_path)
                if not os.path.exists(full_path):
                    os.makedirs(os.path.dirname(full_path))
                mount_pint = self.PACKAGE_SAVE_PATH + package + "/merge" + dir
            if os.path.ismount(mount_pint):
                continue
            try:
                mount_cmd = "mount --bind {} {}".format(dir,mount_pint)
                ret = self.run_command_status_output(mount_cmd)
                self.log.info(f'mount share dir:{mount_pint},resoult:{ret}')
            except Exception as e:
                self.log.error(f"Failed to execute command: {mount_cmd}. Error: {e}")

    """
    :brief 卸载掉通过chroot安装应用而挂载的共享目录
    """
    def umount_share_dirs(self, package):
        self.log.info(f'===================chroot umount共享目录===================')
        for dir in self.get_chroot_share_dirs if  not package == "dkmsContainer" else self.get_chroot_share_dirs + self.get_chroot_dkms_dir:
            umount_cmd = "umount -l {}".format(self.PACKAGE_SAVE_PATH + package + "/merge" + dir)
            ret = self.run_command_status_output(umount_cmd)
            self.log.info(f'umount share dir:{dir},ret = {ret}')


    """
    :brief 构建一个crun的json文件
    :param package_path: 包存储路径
    :param cmd: 容器内所需命令行
    """
    def build_config_json(self, package_path, cmd):
        self.log.info(f'===================chroot 构建crun的config.json===================')
        container = Crun(True,self.log)
        container.exec(package_path, cmd)
        os.chmod(package_path + "/config.json", 0o766)


        
