import gzip
import os

from os.path import join
from contextlib import contextmanager

from paramiko import SSHClient
from ftputil import FTPHost
from ftputil.error import PermanentError


class BaseDestination(object):
    def list(self, hostname, kind, ressource):
        """
        List all backups for a hostname, kind of backup and ressource.

        Must return an iterable sorted by the name of the backup.
        """

    def mkdir(self, hostname, kind, ressource):
        """
        Prepare the directory in which the backup will be stored
        """

    def store_stream(self, hostname, kind, ressource, data, filename):
        """
        Stores a stream data to a file on the destination
        """

    def remove_backup(self, hostname, kind, ressource, filename):
        """
        Removes a backup from the destination given the hostname, kind of
        backup, ressource identifier and backup name
        """


class LocalDestination(object):
    def __init__(self, dest_base_path='/var/backups'):
        self.dest_base_path = self.dest_base_path

    def list(self, hostname, kind, ressource):
        path = join(self.dest_base_path, hostname, kind, ressource)
        olders = os.listdir(path)
        olders = map(lambda o: o.replace('\n', ''), olders)
        olders = sorted(olders)
        return olders

    def mkdir(self, hostname, kind, ressource):
        path = join(self.dest_base_path, hostname, kind, ressource)
        os.makedirs(path)

    def store_stream(self, hostname, kind, ressource, data, filename):
        destination = join(
            self.dest_base_path, hostname, kind, ressource, filename + '.gz'
        )
        gzipfile = gzip.open(destination, "wb")
        for line in data:
            gzipfile.write(line)
        gzipfile.close()

    def remove_backup(self, hostname, kind, ressource, filename):
        destination = join(
            self.dest_base_path, hostname, kind, ressource, filename
        )
        cmd = 'rm -r %s' % (destination)
        self.ssh_exec(cmd)


class UbacSshException(Exception):
    pass


class SshDestination(object):
    def __init__(self, ssh_host='localhost', ssh_port=22, ssh_user='ubac',
                 dest_base_path='/var/backups'):
        self.ssh_host = ssh_host
        self.ssh_port = ssh_port
        self.ssh_user = ssh_user
        self.dest_base_path = dest_base_path

    @classmethod
    def from_section(cls, section):
        return cls(
            ssh_host=section['ssh_host'],
            ssh_port=int(section['ssh_port']),
            ssh_user=section['ssh_user'],
            dest_base_path=section['dest_base_path'],
        )

    @property
    def ssh(self):
        if not hasattr(self, '_ssh'):
            self._ssh = SSHClient()
            self._ssh.load_system_host_keys()
            self._ssh.connect(
                self.ssh_host, port=self.ssh_port, username=self.ssh_user
            )
        return self._ssh

    def close(self):
        if not hasattr(self, '_ssh'):
            return
        self._ssh.close()
        del self._ssh

    def ssh_exec(self, cmd):
        stdin, stdout, stderr = self.ssh.exec_command(cmd)
        status = stdout.channel.recv_exit_status()
        if not status:
            return stdin, stdout, stderr
        raise UbacSshException(
            'Ssh command \'%s\'exited with status code %d ; stdout=%s ; '
            'stderr=%s',
            cmd, status, stdout.readlines(), stderr.readlines()
        )

    def list(self, hostname, kind, ressource):
        directory = join(self.dest_base_path, hostname, kind, ressource)
        try:
            stdin, stdout, stderr = self.ssh_exec(
                'ls %s' % (directory)
            )
        except UbacSshException:
            return []
        olders = stdout.readlines()
        olders = map(lambda o: o.replace('\n', ''), olders)
        olders = sorted(olders)
        return olders

    def mkdir(self, *e):
        directory = join(self.dest_base_path, *e)
        self.ssh_exec('mkdir -p ' + directory)

    def store_stream(self, hostname, kind, ressource, data, filename):
        with self.get_writing_stream(
                hostname, kind, ressource, filename) as stdin:
            for line in data:
                stdin.write(line)

    @contextmanager
    def get_writing_stream(self, hostname, kind, ressource, filename):
        destination = join(
            self.dest_base_path, hostname, kind, ressource, filename + '.gz'
        )
        stdin, stdout, stderr = self.ssh.exec_command(
            'gzip - > ' + destination
        )
        yield stdin
        stdin.close()

    def remove_backup(self, hostname, kind, ressource, filename):
        destination = join(
            self.dest_base_path, hostname, kind, ressource, filename
        )
        cmd = 'rm -r %s' % (destination)
        self.ssh_exec(cmd)


class FtpDestination(object):
    def __init__(self, ftp_host='hd1.freebox.fr',
                 ftp_user='freebox', ftp_password='2009',
                 dest_base_path='/Disque dur/backups'):
        self.ftp_host = ftp_host
        self.ftp_user = ftp_user
        self.ftp_password = ftp_password
        self.dest_base_path = dest_base_path

    @classmethod
    def from_section(cls, section):
        return cls(
            ftp_host=section['ftp_host'],
            ftp_user=section['ftp_user'],
            ftp_password=section['ftp_password'],
            dest_base_path=section['dest_base_path'],
        )

    @property
    def ftp(self):
        if not hasattr(self, '_ftp'):
            self._ftp = FTPHost(
                self.ftp_host,
                self.ftp_user,
                self.ftp_password,
            )
        return self._ftp

    def close(self):
        if not hasattr(self, '_ftp'):
            return
        self._ftp.close()
        del self._ftp

    def list(self, hostname, kind, ressource):
        try:
            directory = join(self.dest_base_path, hostname, kind, ressource)
            olders = self.ftp.listdir(directory)
            olders = map(lambda o: o.replace('\n', ''), olders)
            olders = sorted(olders)
            return olders
        except PermanentError:
            return []

    def mkdir(self, hostname, kind, ressource):
        directory = join(self.dest_base_path, hostname, kind, ressource)
        self.ftp.makedirs(directory)

    def store_stream(self, hostname, kind, ressource, data, filename):
        with self.get_writing_stream(
                hostname, kind, ressource, filename) as stdin:
            for line in data:
                stdin.write(line)

    @contextmanager
    def get_writing_stream(self, hostname, kind, ressource, filename):
        destination = join(
            self.dest_base_path, hostname, kind, ressource, filename
        )
        stream = self.ftp.open(destination, 'wb')
        yield stream
        stream.close()

    def remove_backup(self, hostname, kind, ressource, filename):
        destination = join(
            self.dest_base_path, hostname, kind, ressource, filename
        )
        self.ftp.remove(destination)
