import argparse
import json
import netaddr
import os
import uuid
import docker
import sys
from docker.errors import APIError
from vrouter_netns import validate_uuid, NetnsManager


class VRouterDocker(object):
    """
    Creates and drestoys service instance inside Docker.
    It needs to be run as superuser to connect Docker network to linux netns
    """

    def __init__(self, args_str=None):
        self.args = None
        self._client = docker.Client(version='1.14', timeout=30)
        self._parse_args(args_str, sys.argv[1:])

    def _parse_args(self, args_str=None, argv=None):
        """Return an argparse.ArgumentParser for me"""
        if args_str is not None:
            args = args_str.split()
        elif argv is not None:
            args = argv
        else:
            raise ValueError("argv or args_str must be suplied!")
        print args
        conf_parser = argparse.ArgumentParser(add_help=False)
        args, remaining_argv = conf_parser.parse_known_args(args)
        # Override with CLI options
        # Don't surpress add_help here so it will handle -h
        parser = argparse.ArgumentParser(
            # Inherit options from config_parser
            parents=[conf_parser],
            # print script description with -h/--help
            description=__doc__,
            # Don't mess with format of description
            formatter_class=argparse.RawDescriptionHelpFormatter,
        )
        subparsers = parser.add_subparsers()

        create_parser = subparsers.add_parser('create')
        create_parser.add_argument(
            "vm_id",
            help="Virtual machine UUID")
        create_parser.add_argument(
            "--vmi-left-id",
            default=None,
            help="Left virtual machine interface UUID")
        create_parser.add_argument(
            "--vmi-left-mac",
            default=None,
            help=("Left virtual machine interface MAC. Default: automatically "
                  "generated by the system"))
        create_parser.add_argument(
            "--vmi-left-ip",
            default=None,
            help=("Left virtual machine interface IPv4 and mask "
                  "(ie: a.a.a.a/bb). Default mask to /32"))
        create_parser.add_argument(
            "--vmi-right-id",
            default=None,
            help="Right virtual machine interface UUID")
        create_parser.add_argument(
            "--vmi-right-mac",
            default=None,
            help=("Right virtual machine interface MAC. Default: "
                  "automatically generated by the system"))
        create_parser.add_argument(
            "--vmi-right-ip",
            default=None,
            help=("Right virtual machine interface IPv4 and mask "
                  "(ie: a.a.a.a/bb). Default mask to /32"))
        create_parser.add_argument(
            "--vmi-management-id",
            default=None,
            help="Management virtual machine interface UUID")
        create_parser.add_argument(
            "--vmi-management-mac",
            default=None,
            help=("Management virtual machine interface MAC. Default: "
                  "automatically generated by the system"))
        create_parser.add_argument(
            "--vmi-management-ip",
            default=None,
            help=("Management virtual machine interface IPv4 and mask "
                  "(ie: a.a.a.a/bb). Default mask to /32"))
        create_parser.add_argument(
            "--image",
            help="Image for a Docker container")
        create_parser.add_argument(
            "--command",
            help="Command to run inside a Docker container")
        create_parser.add_argument(
            "--instance-data",
            default=None,
            help="Additional data as JSON string")
        create_parser.add_argument(
            "--update",
            action="store_true",
            default=False,
            help="Update a created Docker container")
        create_parser.set_defaults(func=self.create)

        destroy_parser = subparsers.add_parser('destroy')
        destroy_parser.add_argument(
            "vm_id",
            help="Virtual machine UUID")
        destroy_parser.add_argument(
            "--vmi-left-id",
            help="Left virtual machine interface UUID")
        destroy_parser.add_argument(
            "--vmi-right-id",
            help="Right virtual machine interface UUID")
        destroy_parser.add_argument(
            "--vmi-management-id",
            default=None,
            help="Management virtual machine interface UUID")
        destroy_parser.set_defaults(func=self.destroy)

        self.args = parser.parse_args(remaining_argv)

    @staticmethod
    def _create_nic_def(vmi_id, vmi_mac=None, vmi_ip=None):
        nic = {}
        if uuid.UUID(vmi_id).int:
            nic['uuid'] = validate_uuid(vmi_id)
            if vmi_mac:
                nic['mac'] = netaddr.EUI(vmi_mac, dialect=netaddr.mac_unix)
            else:
                nic['mac'] = None
            if vmi_ip:
                nic['ip'] = netaddr.IPNetwork(vmi_ip)
            else:
                nic['ip'] = None
        return nic

    def _stop(self, vm_name, vmi_left_id, vmi_right_id, vmi_management_id):
        docker_pid = self._client.inspect_container(vm_name)['State']['Pid']
        if vmi_left_id is not None:
            nic_left = self._create_nic_def(self.args.vmi_left_id)
        else:
            nic_left = None

        if vmi_right_id is not None:
            nic_right = self._create_nic_def(self.args.vmi_right_id)
        else:
            nic_right = None

        if vmi_management_id is not None:
            nic_management = self._create_nic_def(self.args.vmi_management_id)
            nic_management = [nic_management]
        else:
            nic_management = []

        netns_mgr = NetnsManager(str(docker_pid), nic_left, nic_right,
                                 nic_management)
        try:
            #It is possible that namespace does not exists
            netns_mgr.unplug_namespace_interface()
            netns_mgr.destroy()
        except ValueError:
            pass

        self._client.stop(vm_name)

        netns_path = "/var/run/netns/%s" % docker_pid
        if os.path.exists(netns_path):
            os.remove(netns_path)

    def create(self):
        vm_name = validate_uuid(self.args.vm_id)
        image_name = self.args.image
        if self.args.instance_data:
            instance_data = json.loads(self.args.instance_data)
        else:
            instance_data = {}

        try:
            self._client.inspect_image(image_name)
        except APIError as e:
            if e.response.status_code == 404:
                self._client.pull(image_name)
                self._client.inspect_image(image_name)
            else:
                raise
        if self.args.command is not None:
            command = self.args.command
        elif "command" in instance_data:
            command = instance_data["command"]
        else:
            # use container default
            command = None
        docker_id = None
        try:
            result = self._client.create_container(
                image=image_name, name=vm_name, command=command, detach=True,
                stdin_open=True, tty=True)  # keep the container running
            docker_id = result["Id"]
            self._stop(vm_name, self.args.vmi_left_id, self.args.vmi_right_id,
                       self.args.vmi_management_id)
        except APIError as e:
            if e.response.status_code == 409:
                if self.args.update:
                    container = self._client.inspect_container(vm_name)
                    docker_id = container["Id"]
                else:
                    raise

        if self.args.vmi_left_id is not None:
            nic_left = self._create_nic_def(self.args.vmi_left_id,
                                            self.args.vmi_left_mac,
                                            self.args.vmi_left_ip)
        else:
            nic_left = None

        if self.args.vmi_right_id is not None:
            nic_right = self._create_nic_def(self.args.vmi_right_id,
                                             self.args.vmi_right_mac,
                                             self.args.vmi_right_ip)
        else:
            nic_right = None

        if self.args.vmi_management_id is not None:
            nic_management = self._create_nic_def(self.args.vmi_management_id,
                                                  self.args.vmi_management_mac,
                                                  self.args.vmi_management_ip)
            nic_management['name'] = ("mng-" + nic_management['uuid']
                                      )[:NetnsManager.DEV_NAME_LEN]
            nic_management = [nic_management]
        else:
            nic_management = []

        self._client.start(docker_id, network_mode='none')
        docker_pid = self._client.inspect_container(docker_id)['State']['Pid']
        netns_mgr = NetnsManager(vm_name, nic_left, nic_right,
                                 other_nics=nic_management,
                                 namespace_name=str(docker_pid))

        if not os.path.exists("/var/run/netns/"):
            os.makedirs("/var/run/netns/")
        if netns_mgr.is_netns_already_exists():
            # If the netns already exists, destroy it to be sure to set it
            # with new parameters like another external network
            netns_mgr.unplug_namespace_interface()
            netns_mgr.destroy()

        # connect docker network stack as new netns on which we will work
        os.symlink("/proc/%s/ns/net" % docker_pid,
                   "/var/run/netns/%s" % docker_pid)
        netns_mgr.create()
        netns_mgr.plug_namespace_interface()

    def destroy(self):
        vm_name = validate_uuid(self.args.vm_id)
        self._stop(vm_name, self.args.vmi_left_id, self.args.vmi_right_id,
                   self.args.vmi_management_id)
        self._client.remove_container(vm_name)




def main(args_str=None):
    vrouter_netns = VRouterDocker(args_str)
    vrouter_netns.args.func()


if __name__ == "__main__":
    main()
