import subprocess
import logging
import logging.config
import socket
import sys

from datetime import datetime, timedelta


logger = logging.getLogger('ubac')
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler(sys.stdout))
logger_check = logging.getLogger('ubac.check')


def parse_list(raw):
    for value in raw.split('\n'):
        value = value.strip()
        if not value:
            continue
        yield value


class UbacSshException(Exception):
    pass


class UbacBase(object):
    def __init__(self, destination, freshness=24, nb_to_keep=7):
        self.destination = destination
        self.freshness = freshness
        self.nb_to_keep = nb_to_keep

    def close(self):
        self.destination.close()


class UbacDatabase(UbacBase):
    def __init__(self, remote, databases, freshness=24, nb_to_keep=7):
        super(UbacDatabase, self).__init__(
            remote, freshness=freshness, nb_to_keep=nb_to_keep
        )
        self.databases = databases

    def list_dbs(self):
        if self.databases:
            for db in self.databases:
                if not db:
                    continue
                yield db
            return
        stdout = self.list_available_dbs()
        for dbname in stdout.decode('utf-8').splitlines():
            dbname = dbname.strip()
            if dbname:
                yield dbname
            else:
                return

    def list_available_dbs(self):
        raise NotImplementedError

    def dump_db(self, cmd):
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        for line in p.stdout:
            yield line
        assert p.wait() == 0

    def single_db(self, dbname):
        logger.info('backuping %s db %s', self.DEST_SUBDIR, dbname)

        self.destination.mkdir(socket.gethostname(), self.DEST_SUBDIR, dbname)
        stream = self.dump_db(dbname)
        filename = datetime.utcnow().strftime('%Y%m%d_%H%M%S') + '.sql'
        self.destination.store_stream(
            socket.gethostname(), self.DEST_SUBDIR, dbname, stream, filename
        )

    def backup_all(self):
        for dbname in self.list_dbs():
            self.single_db(dbname)
            self.clean(dbname)

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

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

    def clean(self, dbname):
        backups = self.destination.list(
            socket.gethostname(), self.DEST_SUBDIR, dbname
        )
        for to_del in sorted(backups, reverse=True)[self.nb_to_keep:]:
            logger.info(
                'Removing backup %s of %s db %s',
                to_del, self.DEST_SUBDIR, dbname
            )
            self.destination.remove_backup(
                socket.gethostname(), self.DEST_SUBDIR, dbname, to_del
            )
