import argparse
import json
import os

from . import util
from . import Atomic
from .client import AtomicDocker
import datetime
from dateutil.parser import parse as dateparse
from docker.errors import NotFound, APIError

try:
    from subprocess import DEVNULL  # pylint: disable=no-name-in-module
except ImportError:
    DEVNULL = open(os.devnull, 'wb')

def cli(subparser):
    # atomic containers
    c = subparser.add_parser("containers",
                             help=_("operate on containers"))
    containers_subparser = c.add_subparsers(title='containers subcommands',
                                            description="operate on containers",
                                            help='additional help')
    # atomic containers delete
    delete_parser = containers_subparser.add_parser("delete",
                                                help=_("delete specified containers"))
    delete_parser.set_defaults(_class=Containers, func='delete_image')
    delete_parser.add_argument("-f", "--force", action='store_true',
                               dest="force",
                               default=False,
                               help=_("Force removal of specified running containers"))
    delete_group = delete_parser.add_mutually_exclusive_group()
    delete_group.add_argument("-a", "--all", action='store_true',dest="all",
                               default=False,
                               help=_("Delete all containers"))
    delete_group.add_argument("container", nargs='?', action="append", 
                              help=_("Specify one or more containers. Must be final arguments."))
    delete_parser.add_argument("containers", nargs=argparse.REMAINDER, 
                               help=argparse.SUPPRESS)
    delete_parser.set_defaults(_class=Containers, func='delete')


    # atomic containers list
    pss = containers_subparser.add_parser("list",
                                          help=_("list the containers"),
                                          epilog="By default this shows only the running containers.")
    pss.set_defaults(_class=Containers, func='ps_tty')
    pss.add_argument("-a", "--all", action='store_true',dest="all", default=False,
                     help=_("show all containers"))
    pss.add_argument("-f", "--filter", metavar='FILTER', action='append', dest="filter",
                     help=_("Filter output based on conditions given in the VARIABLE=VALUE form"))
    pss.add_argument("--json", action='store_true',dest="json", default=False,
                     help=_("print in a machine parseable form"))
    pss.add_argument("-n", "--noheading", dest="heading", default=True,
                     action="store_false",
                     help=_("do not print heading when listing the containers"))
    pss.add_argument("--no-trunc", action='store_false', dest="truncate", default=True,
                     help=_("Don't truncate output"))
    pss.add_argument("-q", "--quiet", action='store_true', dest="quiet", default=False,
                     help=_("Only display container IDs"))
    # atomic containers trim
    trimp = containers_subparser.add_parser("trim",
                                            help=_("discard unused blocks (fstrim) on running containers"),
                                            epilog="Discard unused blocks (fstrim) on rootfs of running containers.")
    trimp.set_defaults(_class=Containers, func='fstrim')

class Containers(Atomic):

    def fstrim(self):
        with AtomicDocker() as client:
            for container in client.containers():
                containerId = container["Id"]
                ret = self._inspect_container(name=containerId)
                pid = ret["State"]["Pid"]
                mp = "/proc/%d/root" % (pid)
                util.write_out("Trimming container id {0}".format(containerId[0:12]))
                util.check_call(["/usr/sbin/fstrim", "-v", mp], stdout=DEVNULL)
        return

    def ps_tty(self):
        all_container_info = self.ps()
        all_containers = []
        for each in all_container_info:
            if each["Type"] == "system":
                container = each["Id"]
                status = "exited"
                created = datetime.datetime.fromtimestamp(each["Created"])
                info = self.syscontainers.get_container_runtime_info(container)
                if 'status' in info:
                    status = info["status"]

                if not self.args.all and status != "running":
                    continue

                image = each['Image']
                imageId = each['ImageID']
                command = each["Command"]
                created = created.strftime("%F %H:%M") # pylint: disable=no-member
                container_info = {"type" : "system", "container" : container,
                              "image" : image, "command" : command, "image_id" : imageId,
                              "created" : created, "status" : status,
                              "runtime" : "runc", "vulnerable" : each["vulnerable"]}

                if self.args.filter:
                    if not self._filter_include_container(container_info):
                        continue
                all_containers.append(container_info)

            elif each["Type"] == "docker":
            # Collect the docker containers
                container = each["Id"]
                ret = self._inspect_container(name=container)
                status = ret["State"]["Status"]
                image = ret['Config']['Image']
                imageId = ret['Image']
                command = u' '.join(ret['Config']['Cmd']) if ret['Config']['Cmd'] else ""
                created = dateparse(ret['Created']).strftime("%F %H:%M") # pylint: disable=no-member
                container_info = {"type" : "docker", "container" : container,
                                  "image" : image, "image_id" : imageId, "command" : command,
                                  "created" : created, "status" : status,
                                  "runtime" : "Docker", "vulnerable" : each["vulnerable"]}

                if self.args.filter:
                    if not self._filter_include_container(container_info):
                        continue
                all_containers.append(container_info)

        if not all_containers:
            return

        if self.args.json:
            util.write_out(json.dumps(all_containers))
            return

        if self.args.truncate:
            max_len_container = 12
            max_len_image = 20
            max_len_command = 20
        else:
            max_len_container = max(max([len(s["container"]) for s in all_containers]), 12)
            max_len_image = max(max([len(s["image"]) for s in all_containers]), 20)
            max_len_command = max(max([len(s["command"]) for s in all_containers]), 20)

        if self.args.quiet:
            for container in all_containers:
                util.write_out(container["container"][0:max_len_container])
            return

        col_out = "{0:2} {1:%s} {2:%s} {3:%s} {4:16} {5:9} {6:10}" % (max_len_container, max_len_image, max_len_command)

        if self.args.heading:
            util.write_out(col_out.format(" ",
                                          "CONTAINER ID",
                                          "IMAGE",
                                          "COMMAND",
                                          "CREATED",
                                          "STATUS",
                                          "RUNTIME"))

        #if self.args.truncate:
        for container in all_containers:
            indicator = ""
            if container["vulnerable"]:
                if util.is_python2:
                    indicator = indicator + self.skull + " "
                else:
                    indicator = indicator + str(self.skull, "utf-8") + " "
            util.write_out(col_out.format(indicator,
                                          container["container"][0:max_len_container],
                                          container["image"][0:max_len_image],
                                          container["command"][0:max_len_command],
                                          container["created"][0:16],
                                          container["status"][0:9],
                                          container["runtime"][0:10]))
    def ps(self):
        all_containers = []
        vuln_ids = self.get_vulnerable_ids()
        all_vuln_info = json.loads(self.get_all_vulnerable_info())

        # Collect the system containers
        for i in self.syscontainers.get_containers():
            i["vulnerable"] = i['Id'] in vuln_ids
            if i["vulnerable"]:
                i["vuln_info"] = all_vuln_info[i['Id']]
            else:
                i["vuln_info"] = dict()
            all_containers.append(i)

        # Collect the docker containers
        for container in [x["Id"] for x in self.d.containers(all=self.args.all)]:
            ret = self._inspect_container(name=container)
            ret["Type"] = "docker"
            ret["vulnerable"] = ret["Image"] in vuln_ids
            if ret["vulnerable"]:
                ret["vuln_info"] = all_vuln_info[ret["Image"]]
            else:
                ret["vuln_info"] = dict()
            all_containers.append(ret)

        return all_containers

    def _filter_include_container(self, container_info):
        filterables = ["container", "image", "command", "created", "status", "runtime"]
        for j in self.args.filter:
            var, value = str(j).split("=")
            var = var.lower()

            if var == "id" or var == "containerid":
                var = "container"

            if var not in filterables: # If the filter does not exist, default to allowing all containers through
                continue

            if value not in container_info[var]:
                return False

        return True

    def delete(self):
        results = 0
        sys_targets=[]
        docker_targets=[]
        if self.args.all:
            for c in self.get_containers():
                docker_targets.append(c["Id"])
            for c in self.syscontainers.get_containers():
                sys_targets.append(c["Id"])
        else:
            if not self.args.container and len(self.args.containers) == 0:
                raise ValueError("No containers selected")
            for c in [ self.args.container ] + self.args.containers:
                if self.syscontainers.get_checkout(c):
                    sys_targets.append(c)
                else:
                    docker_targets.append(c)

        for target in docker_targets:
            try:
                self.d.remove_container(target, force=self.args.force)
            except NotFound as e:
                util.write_err("Failed to delete container {}: {}".format(target, e))
                results += 1
            except APIError as e:
                util.write_err("Failed operation for delete container {}: {}".format(target, e))
                results += 1

        for target in sys_targets:
            try:
                self.syscontainers.uninstall(target)
            except IOError as e:
                util.write_err("Failed to delete container {}: {}".format(target, e))
                results += 1
        return results
