#!/usr/bin/env python
import logging
import os
import re
import ssl
import sys
from copy import copy
from optparse import OptionParser, Option

import dateutil.parser as dateparser
from imbox import Imbox
from jinja2 import Template, UndefinedError

from attachment_downloader.cli import valid_date, get_password
from attachment_downloader.encoding import QuoPriEncoding
from attachment_downloader.logging import Logger
from attachment_downloader.version_info import Version


def build_filter_options():
    filter_options = {'folder': folder}
    if options.date_after and options.date_before and options.date_after.date() == options.date_before.date():
        filter_options['date__on'] = options.date_after.date()
    else:
        if options.date_after:
            filter_options['date__gt'] = options.date_after.date()
        if options.date_before:
            filter_options['date__lt'] = options.date_before.date()
    logging.info("Listing messages matching the following criteria: %s",
                 ", ".join([k + "=" + str(v) for k, v in filter_options.items()]))
    return filter_options

def process_message(filename_template, options, message):
    subject = ''
    if hasattr(message, 'subject'):
        subject = QuoPriEncoding.decode(message.subject)

    try:
        parsed_message_date = dateparser.parse(message.date)
    except Exception as e:
        logging.exception(e)
        if hasattr(message, 'date'):
            logging.error("Skipping message '%s' subject '%s' because date can not be parsed '%s'", uid,
                          subject, message.date)
        else:
            logging.error("Skipping message '%s' subject '%s' because message does not have a date attribute",
                          uid, subject)
        return

    if options.date_after and parsed_message_date < options.date_after:
        logging.warning("Skipping message '%s' subject '%s' because it is before %s", uid, subject,
                        options.date_after)
        return

    if options.date_before and parsed_message_date >= options.date_before:
        logging.warning("Skipping message '%s' subject '%s' because it is after %s", uid, subject,
                        options.date_before)
        return

    if options.subject_regex and not re.match(options.subject_regex, subject):
        logging.warning("Skipping message '%s' subject '%s' because it does not match %s", uid, subject,
                        options.subject_regex)
        return

    logging.info("Processing message '%s' subject '%s'", uid, subject)

    for idx, attachment in enumerate(message.attachments):
        try:
            attachment_filename = QuoPriEncoding.decode(attachment.get('filename'))
            download_filename = filename_template.render(attachment_name=attachment_filename,
                                                         attachment_idx=idx,
                                                         subject=subject,
                                                         message_id=message.message_id,
                                                         date=parsed_message_date,
                                                         folder=folder)

            download_path = os.path.join(options.download_folder, download_filename)
            os.makedirs(os.path.dirname(os.path.abspath(download_path)), exist_ok=True)
            logging.info("Downloading attachment '%s' to path %s", attachment_filename, download_path)

            if os.path.isfile(download_path):
                logging.warning("Overwriting file: '%s'", download_path)

            with open(download_path, "wb") as fp:
                fp.write(attachment.get('content').read())
        except UndefinedError as e:
            logging.error('Filename template contains an undefined variable: %s', str(e))
            logging.info('Logging out of: %s', options.host)
            mail.logout()
            exit(-1)
        except Exception as e:
            logging.exception(e)
            logging.error('Error saving file. Continuing...')
        else:
            if options.delete:
                if options.delete_copy_folder:
                    mail.copy(uid, '"' + options.delete_copy_folder + '"')

                mail.delete(uid)

if __name__ == '__main__':
    if sys.version_info[0] < 3:
        print("This application requires Python 3+, you are running version: " + sys.version)
        exit(1)

    class AttachmentDownloaderOption(Option):
        TYPES = Option.TYPES + ('date',)
        TYPE_CHECKER = copy(Option.TYPE_CHECKER)
        TYPE_CHECKER['date'] = valid_date


    parser = OptionParser(option_class=AttachmentDownloaderOption)
    parser.add_option("--host", dest="host", help="IMAP Host")
    parser.add_option("--username", dest="username", help="IMAP Username")
    parser.add_option("--password", dest="password", help="IMAP Password")
    parser.add_option("--imap-folder", dest="imap_folder", help="IMAP Folder to extract attachments from")
    parser.add_option("--subject-regex", dest="subject_regex", help="Regex that the subject must match against")
    parser.add_option("--date-after", dest="date_after", type='date',
                      help='Select messages after this date')
    parser.add_option("--date-before", dest="date_before",
                      type='date',
                      help='Select messages before this date')
    parser.add_option("--filename-template", dest="filename_template", help="Attachment filename (jinja2) template.",
                      default="{{ attachment_name }}")
    parser.add_option("--output", dest="download_folder", help="Output directory for attachment download")
    parser.add_option("--delete", dest="delete", action="store_true", help="Delete downloaded emails from Mailbox")
    parser.add_option("--delete-copy-folder", dest="delete_copy_folder",
                      help="IMAP folder to copy emails to before deleting them")
    parser.add_option("--port", dest="port", type=int,
                      help="Specify imap server port (defaults to 993 for TLS and 143 otherwise")
    parser.add_option("--unsecure", dest="unsecure", action="store_true",
                      help="disable encrypted connection (not recommended)")
    parser.add_option("--starttls", dest="starttls", action="store_true", help="enable STARTTLS (not recommended)")
    parser.add_option("--log-level", dest="loglevel", default="INFO",
                      help="Set the log level to DEBUG, INFO, WARNING, ERROR, or CRITICAL (default is INFO)")

    (options, args) = parser.parse_args()

    Logger.setup(options.loglevel)
    logging.info(f'Attachment Downloader - Version: {Version.get()} {Version.get_env_info()}')

    if not options.host:
        parser.error('--host parameter required')
    if not options.username:
        parser.error('--username parameter required')
    if not options.download_folder:
        parser.error('--output parameter required')
    if options.delete_copy_folder and not options.delete:
        parser.error('--delete parameter required when using --delete-copy-folder')
    if options.unsecure and options.starttls:
        parser.error('--unsecure and --starttls are exclusive')

    tls = False if options.unsecure else True
    if options.starttls:
        starttls = True
        tls = False
    else:
        starttls = False

    filename_template = Template(options.filename_template)

    password = options.password if options.password else get_password()

    logging.info("Logging in to: '%s' as '%s'", options.host, options.username)
    mail = Imbox(options.host,
                 username=options.username,
                 password=password,
                 port=options.port,
                 ssl=tls,
                 ssl_context=ssl.create_default_context(),
                 starttls=starttls)

    logging.info("Logged in to: '%s' as '%s'", options.host, options.username)

    folders = []

    if options.imap_folder:
        folders = [options.imap_folder]
    else:
        status, folders = mail.folders()
        folders = [folder.decode().split(" \"/\" ")[1] for folder in folders]
        folders = [f for f in folders if not f == '"[Gmail]"']

    logging.info("Folders: %s", folders)
    for folder in folders:
        filter_options = build_filter_options()

        try:
            messages = mail.messages(**filter_options)

            for (uid, message) in messages:
                process_message(filename_template, options, message)
        except Exception as e:
            logging.error("Failed to process messages for folder '%s'", folder)
            logging.exception(e)

    logging.info('Finished processing messages')

    logging.info('Logging out of: %s', options.host)
    mail.logout()

    logging.info("Done")
