import logging
import ftplib
import os
import re

from flexget import plugin
from flexget.event import event
from flexget.entry import Entry
from flexget.config_schema import one_or_more

log = logging.getLogger('ftp_list')


class InputFtpList(object):
    """
    Generate entries from a ftp listing

    Configuration:
      ftp_list:
        config:
          name: <ftp name>
          username: <username>
          password: <password>
          host: <host to connect>
          port: <port>
          use-ssl: <yes/no>
          encoding: <auto/utf8/ascii>
          files-only: <yes/no>
          recursive: <yes/no>
          get-size: <yes/no>
        dirs:
          - <directory 1>
          - <directory 2>
          - ....
    """
    encodings = ['auto', 'utf8', 'ascii']
    schema = {
        'type': 'object',
        'properties': {
            'config': {
                'type': 'object',
                'properties': {
                    'name': {'type': 'string'},
                    'username': {'type': 'string'},
                    'password': {'type': 'string'},
                    'host': {'type': 'string'},
                    'port': {'type': 'integer'},
                    'use-ssl': {'type': 'boolean', 'default': False},
                    'encoding': {'type': 'string', 'enum': encodings, 'default': 'auto'},
                    'files-only': {'type': 'boolean', 'default': False},
                    'recursive': {'type': 'boolean', 'default': False},
                    'get-size': {'type': 'boolean', 'default': True}
                },
                'additionProperties': False,
                'required': ['name', 'username', 'password', 'host', 'port'],
            },
            'dirs': one_or_more({'type': 'string'}),
        },
        'required': ['config'],
        'additionalProperties': False
    }

    def on_task_input(self, task, config):
        connection_config = config['config']

        if connection_config['use-ssl']:
            ftp = ftplib.FTP_TLS()
        else:
            ftp = ftplib.FTP()

        # ftp.set_debuglevel(2)
        log.debug('Trying connecting to: %s', (connection_config['host']))
        try:
            ftp.connect(connection_config['host'], connection_config['port'])
            ftp.login(connection_config['username'], connection_config['password'])
        except ftplib.all_errors as e:
            raise plugin.PluginError(e)

        log.debug('Connected.')

        encoding = connection_config['encoding']
        files_only = connection_config['files-only']
        recursive = connection_config['recursive']
        get_size = connection_config['get-size']
        mlst_supported = False

        feat_response = ftp.sendcmd('FEAT').splitlines()
        supported_extensions = [feat_item.strip().upper() for feat_item in feat_response[1:len(feat_response) - 1]]

        if encoding.lower() == 'auto' and 'UTF8' in supported_extensions:
            encoding = 'utf8'
        else:
            encoding = 'ascii'

        for supported_extension in supported_extensions:
            if supported_extension.startswith('MLST'):
                mlst_supported = True
                break

        if not mlst_supported:
            log.warning('MLST Command is not supported by the FTP server %s@%s:%s', connection_config['username'],
                        connection_config['host'], connection_config['port'])

        ftp.sendcmd('TYPE I')
        ftp.set_pasv(True)
        entries = []

        for path in config['dirs']:
            baseurl = "ftp://%s:%s@%s:%s/" % (connection_config['username'], connection_config['password'],
                                              connection_config['host'], connection_config['port'])

            self._handle_path(entries, ftp, baseurl, path, mlst_supported, files_only, recursive, get_size, encoding)

        return entries

    def _handle_path(self, entries, ftp, baseurl, path='', mlst_supported=False, files_only=False, recursive=False,
                     get_size=True, encoding=None):
        dirs = self.list_directory(ftp, path)

        for p in dirs:
            if encoding:
                p = p.decode(encoding)

            #Clean file list when subdirectories are used
            p = p.replace(path + '/', '')

            mlst = {}
            if mlst_supported:
                mlst_output = ftp.sendcmd('MLST ' + path + '/' + p)
                clean_mlst_output = [line.strip().lower() for line in mlst_output.splitlines()][1]
                mlst = self.parse_mlst(clean_mlst_output)
            else:
                element_is_directory = self.is_directory(ftp, path + '/' + p)
                if element_is_directory:
                    mlst['type'] = 'dir'
                    log.debug('%s is a directory', p)
                else:
                    mlst['type'] = 'file'
                    log.debug('%s is a file', p)

            if recursive and mlst.get('type') == 'dir':
                self._handle_path(entries, ftp, baseurl, path + '/' + p, mlst_supported, files_only,
                                  recursive, get_size, encoding)

            if not files_only or mlst.get('type') == 'file':
                url = baseurl + path + '/' + p
                url = url.replace(' ', '%20')
                title = os.path.basename(p)
                log.info('Accepting entry "%s" [%s]' % (path + '/' + p, mlst.get('type') or "unknown",))
                entry = Entry(title, url)
                if get_size and not 'size' in mlst:
                    if mlst.get('type') == 'file':
                        entry['content_size'] = ftp.size(path + '/' + p) / (1024 * 1024)
                        log.debug('(FILE) Size = %s', entry['content_size'])
                    elif mlst.get('type') == 'dir':
                        entry['content_size'] = self.get_folder_size(ftp, path, p)
                        log.debug('(DIR) Size = %s', entry['content_size'])
                elif get_size:
                    entry['content_size'] = float(mlst.get('size')) / (1024 * 1024)
                entries.append(entry)
        
    def parse_mlst(self, mlst):
        re_results = re.findall('(.*?)=(.*?);', mlst)
        parsed = {}
        for k, v in re_results:
            parsed[k] = v
        return parsed

    def is_directory(self, ftp, elementpath):
        try:
            original_wd = ftp.pwd()
            ftp.cwd(elementpath)
            ftp.cwd(original_wd)
            return True
        except ftplib.error_perm:
            return False

    def list_directory(self, ftp, path):
        try:
            dirs = ftp.nlst(path)
        except ftplib.error_perm as e:
            # ftp returns 550 on empty dirs
            if str(e).startswith('550 '):
                log.debug('Directory %s is empty.', path)
                dirs = []
            else:
                raise plugin.PluginError(e)
        return dirs

    def get_folder_size(self, ftp, path, p):
        size = 0
        dirs = self.list_directory(ftp, path + '/' + p)

        for filename in dirs:
            filename = filename.replace(path + '/' + p + '/', '')
            try:
                size += ftp.size(path + '/' + p + '/' + filename) / (1024 * 1024)
            except ftplib.error_perm:
                size += self.get_folder_size(ftp, path + '/' + p, filename)
        return size


@event('plugin.register')
def register_plugin():
    plugin.register(InputFtpList, 'ftp_list', api_ver=2)
