import subprocess
import socket
import json
import sys

from datetime import datetime, timedelta
from os.path import join, basename, dirname

from . import UbacBase, logger, logger_check


class UbacDocker(UbacBase):
    DEST_SUBDIR = 'docker'

    def __init__(self, *args, **kwargs):
        super(UbacDocker, self).__init__(*args, **kwargs)
        self.now = datetime.utcnow().strftime('%Y%m%d_%H%M%S')

    @classmethod
    def from_section(cls, section, remote):
        return cls(
            remote,
            freshness=int(section['check_since']),
            nb_to_keep=int(section['nb_to_keep']),
        )

    def backup_all(self):
        for container_id in self.list_containers():
            try:
                self.save_single(container_id)
                self.clean(container_id)
            except KeyboardInterrupt:
                sys.exit(-1)
            except:
                logger.exception(
                    'Error backuping docker container %s', container_id
                )

    def list_containers(self):
        p = subprocess.Popen(['docker', 'ps', '-q'], stdout=subprocess.PIPE)
        for line in p.stdout:
            yield line.decode()[:-1]
        assert p.wait() == 0

    def save_single(self, container_id):
        logger.info(
            'backuping %s container %s', self.DEST_SUBDIR, container_id
        )
        inspect_content = self.docker_inspect(container_id)
        inspect_dest = join(
            self.destination.dest_base_path,
            socket.gethostname(),
            self.DEST_SUBDIR,
            container_id, self.now, 'inspect.json'
        )
        self.destination.ssh_exec('mkdir -p ' + dirname(inspect_dest))
        stdin, stdout, stderr = self.destination.ssh.exec_command(
            'cat > %s' % (inspect_dest)
        )
        stdin.write(inspect_content)
        stdin.close()

        inspect = json.loads(inspect_content)
        for obj in inspect:
            for mountpoint, storage in obj.get('Volumes', {}).items():
                volume = basename(storage)
                dest_path = join(
                    self.destination.dest_base_path,
                    socket.gethostname(),
                    self.DEST_SUBDIR,
                    container_id,
                    self.now,
                    volume,
                )
                if dest_path[-1] != '/':
                    dest_path += '/'
                self.destination.ssh_exec('mkdir -p ' + dest_path)

                command = 'rsync -az --delete -e'.split()
                command += ['ssh -p %d' % (self.destination.ssh_port)]
                command += [storage + '/', '%s@%s:%s/' % (
                    self.destination.ssh_user, self.destination.ssh_host, dest_path
                )]
                subprocess.check_call(command)

    def docker_inspect(self, container_id):
        return (
            subprocess.check_output(['docker', 'inspect', container_id])
            .decode()
        )

    def clean(self, container_id):
        dest_dir = join(
            self.destination.dest_base_path,
            socket.gethostname(),
            self.DEST_SUBDIR,
            container_id,
        )
        backups = self.destination.list(
            socket.gethostname(),
            self.DEST_SUBDIR,
            container_id,
        )
        for to_del in sorted(backups, reverse=True)[self.nb_to_keep:]:
            logger.info(
                'Removing backup %s of %s container %s',
                to_del, self.DEST_SUBDIR, container_id
            )
            cmd = 'rm -r %s' % (join(dest_dir, to_del))
            self.destination.ssh_exec(cmd)

    def check_all(self):
        result = True
        for container_id in self.list_containers():
            try:
                self.check(container_id)
            except AssertionError as e:
                logger_check.error(*e.args)
                result = False
        return result

    def check(self, container_id):
        backups = self.destination.list(
            socket.gethostname(), self.DEST_SUBDIR, container_id
        )
        assert backups, "No backup found for %s container %s" % (
            self.DEST_SUBDIR, container_id
        )
        last = backups[-1]
        last_date = datetime.strptime(last, '%Y%m%d_%H%M%S')
        last_age = datetime.utcnow() - last_date
        assert last_age < timedelta(hours=self.freshness), (
            'last backup for %s container %s is %s' % (
                self.DEST_SUBDIR, container_id, last_age
            )
        )
        logger.info(
            'Backup of %s container %s is fresh enough',
            self.DEST_SUBDIR,
            container_id,
        )
        return True
