#!/usr/bin/env python3
#
# Very basic example of using Python 3 and IMAP to iterate over emails in a
# gmail folder/label.  This code is released into the public domain.
#
# This script is example code from this blog post:
# http://www.voidynullness.net/blog/2013/07/25/gmail-email-with-python-via-imap/
#
# This is an updated version of the original -- modified to work with Python 3.4.
#
import sys
import imaplib
import os
import sys
import email
import email.header
from IPython import embed
import smtplib
from email.message import EmailMessage
import re
import datetime as dt

EMAIL_ACCOUNT = "bzigr02"
EMAIL_FOLDER = "INBOX"
EMAIL_ADDRESS = 'etho2020registration@biologie.uni-tuebingen.de'
EMAIL_REPLY = 'etho2020@biologie.uni-tuebingen.de'
SMTP_SERVER = 'smtpserv.uni-tuebingen.de'
IMAP_SERVER = 'mailserv.uni-tuebingen.de'
EMAIL_PSWD = "r.8-*0Fc"

PARTICIPANTS_FOLDER = 'participants'

reg_fees = {'early': {"proMember": 95, "proNonMember":115, "studentMember": 55, "studentNonMember": 65},
            'late': {"proMember": 115, "proNonMember":135, "studentMember": 65, "studentNonMember": 75}}
farewell_fee = {"senior": 45, "student": 25}
early_bird = dt.datetime.fromisoformat('2020-01-16T00:00:00')


class Participation(object):
    field_mapping = [("First name", "_first_name"), ("Last name", "_last_name"),
                    ("Institution", "_institution", True), ("Street", "_address_street"),
                    ("City", "_address_city"), ("ZIP code", "_address_zip"),
                    ("Phone", "_phone"), ("e-Mail", "_email"),
                    ("Country", "_address_country"), ("Fee", "_role"),
                    ("Messages", "_message"), ("Food preferences", "_food_preference"),
                    ("Farewell event", "_farewell"), ("Gwinner", "_gwinner"),
                    ("Lab Tour (Fri 21 Feb)", "_lab_tour", True)]

    def __init__(self, email_message):
        self._registration_date = ""
        self._first_name = ""
        self._last_name = ""
        self._phone = ""
        self._email = ""
        self._farewell = ""
        self._gwinner = ""
        self._lab_tour = ""
        self._food_preference = ""
        self._food_vegi = False
        self._food_vegan = False
        self._food_gluten = False
        self._food_normal = True
        self._address_street = ""
        self._address_city = ""
        self._address_zip = ""
        self._address_country = ""
        self._institution = ""
        self._role = ""
        self._message = ""
        self._fee = 0.0
        self._conflicts = []
        self.__parse_message(email_message)

    def __parse_message(self, message):
        date_tuple = email.utils.parsedate_tz(message['Date'])
        if date_tuple:
            local_date = dt.datetime.fromtimestamp(
                email.utils.mktime_tz(date_tuple))
        self._registration_date = local_date

        msg_body = message.get_payload()[0].as_string()
        msg_body = re.sub(r'=20', ' ', msg_body)
        msg_body = re.sub(r'=\n', '', msg_body)
        msg_body = re.sub(r'=E2=82=AC', 'EUR', msg_body)
        msg_body = re.sub(r'&', 'and', msg_body)
        lines = msg_body.split('\n')

        for fm in Participation.field_mapping:
            self.__parse_lines(lines, fm)
        self.__process_food(lines)
        self.__check()

    def __check(self):
        if "student" not in self._role and self.gwinner_award:
            self._conflicts.append("Gwinner award selected but not student!")
        if (self._food_vegan or self._food_gluten or self.vegetarian) and self._food_normal:
            self._conflicts.append("Conflict in food preferences!")

    def __calc_fee(self):
        fee = .0
        early_or_late = "early" if self.registration_date < early_bird else "late"
        pro_or_stud = "senior" if "pro" in self._role else "student"
        fee += reg_fees[early_or_late][self._role]
        fee += farewell_fee[pro_or_stud] if self.farewell_event else 0.0
        return fee

    def __parse_lines(self, lines, field_map):
        assert(len(field_map) >= 2)

        ls = [l for l in lines if l.lower().startswith(field_map[0].lower())]
        if len(ls) < 1:
            print("%s not found: " % field_map[0])
            return
        if len(field_map) == 2:
            setattr(self, field_map[1], ls[0].split(field_map[0])[-1].strip())
        else:
            setattr(self, field_map[1], ', '.join(map(lambda x:x.split(field_map[0])[-1].strip(), ls)))

    def __process_food(self, lines):
        if len(self._food_preference) > 0:
            prefs = self._food_preference.lower()
            self._food_normal = "no special" in prefs
            self._food_vegan = "vegan" in prefs
            self._food_vegi = "vegetarian" in prefs
            self._food_gluten = "gluten" in prefs

    @property
    def registration_date(self):
        return self._registration_date

    @property
    def fee(self):
        return self.__calc_fee()

    @property
    def farewell_event(self):
        return len(self._farewell) > 0

    @property
    def gwinner_award(self):
        return len(self._gwinner) > 0

    @property
    def labtour(self):
        return self._lab_tour.split(",")

    @property
    def valid(self):
        return len(self._conflicts) == 0

    @property
    def name(self):
        return "%s %s" % (self._first_name, self._last_name)

    def save_to_csv(self, filename=None):
        if not os.path.exists(PARTICIPANTS_FOLDER):
            os.mkdir(PARTICIPANTS_FOLDER)
        if not filename:
            filename = os.path.join(PARTICIPANTS_FOLDER, "".join(p.name.split())) + ".csv"
        header = ["LastName", "FirstName", "eMail", "Phone", "Institute", "Street", "City",
                  "RegDate", "CareerStage", "Fee", "FarewellEvent", "GwinnerAward", "LabTour",
                  "FoodVegetarian", "FoodVegan", "FoodGlutenFree", "FoodNoPref"]
        values = [self._first_name, self._last_name, self._email, self._phone, self._institution,
                  self._address_street, "%s %s" % (self._address_zip, self._address_city),
                  self.registration_date.isoformat(), self._role, str(self.fee), str(self.farewell_event),
                  str(self.gwinner_award), str(self.labtour), str(self._food_vegi), str(self._food_vegan),
                  str(self._food_gluten), str(self._food_normal)]
        with open(filename, "w") as f:
            f.write("; ".join(header))
            f.write("\n")
            f.write("; ".join(values))
        return filename
    def __str__(self):
        str = ""
        str += "Name: %s %s\n" % (self._first_name, self._last_name)
        str += "Institution: %s\n" % self._institution
        str += "Phone: %s\n" % self._phone
        str += "e-Mail: %s\n" % self._email
        str += "Career stage: %s %s\n" % ("senior" if "pro" in self._role else "student",
                                          "non-member" if "NonMember" in self._role else "member of Ethological Soc.")
        str += "Farewell event: %s\n" % "Yes" if self.farewell_event else "No"
        str += "Fee: %3.2f EUR\n" % self.fee
        str += "Lab tour: %s\n" % ( "No" if len(self.labtour) == 0 else "Yes, " + ", ".join(self.labtour))
        str += "Gwinner award: %s\n" % "Yes" if self.gwinner_award else "No"
        str += "Food preferences: %s, %s, %s, %s\n" % ("no prefs" if self._food_normal else "",
                                                       "vegetarian" if self._food_vegi else "",
                                                       "vegan" if self._food_vegan else "",
                                                       "gluten free" if self._food_gluten else "")
        str += "Registration date: %s\n" % self.registration_date.isoformat()
        str += "Message:%s\n" % self._message
        if not self.valid:
            str += "Inconsistencies: %s\n" % ", ".join(self._conflicts)
        return str


def process_mailbox(M):
    """
    Do something with emails messages in the folder.  
    For the sake of this example, print some headers.
    """

    rv, data = M.search(None, "UNSEEN")
    if rv != 'OK':
        print("No messages found!")
        return

    for num in data[0].split():
        rv, data = M.fetch(num, '(RFC822)')
        if rv != 'OK':
            print("ERROR getting message", num)
            return

        msg = email.message_from_bytes(data[0][1])
        hdr = email.header.make_header(email.header.decode_header(msg['Subject']))
        subject = str(hdr)
        print('Message %s: %s' % (num, subject))
        print('Raw Date:', msg['Date'])
        # Now convert to local date-time
        date_tuple = email.utils.parsedate_tz(msg['Date'])
        if date_tuple:
            local_date = dt.datetime.fromtimestamp(
                email.utils.mktime_tz(date_tuple))
            print ("Local Date:", \
                local_date.strftime("%a, %d %b %Y %H:%M:%S"))


def process_message(msg_index):
    rv, data = M.fetch(msg_index.encode('ascii'), '(RFC822)')
    if rv != 'OK':
        print("ERROR getting message", rv)
        return
    msg = email.message_from_bytes(data[0][1])
    return Participation(msg)


def process_registrations(M):
    rv, msgs = M.search(None, "SUBJECT", "etho2020", "UNSEEN")
    participations = []
    if rv != "OK":
        print("ERROR searching messages", rv)
        return participations

    msgs = msgs[0].decode('ascii').split()
    print("Found %i registration mails" % len(msgs))
    for msg_index in msgs:
        participations.append(process_message(msg_index))
    return participations


def send_confirmation(p=None, csv_file=None):
    if p is not None:
        assert(isinstance(p, Participation))
    content = "Dear %s," % (p.name if p else "participant")
    with open("response.txt") as fp:
        content += fp.read()
    content += "\n"
    content += str(p) if p else ""
    msg = EmailMessage()
    msg.set_content(content)
    msg['Subject'] = 'Etho 2020 registration confirmation'
    msg['From'] = EMAIL_ADDRESS
    msg['To'] = p._email
    msg['Reply-to'] = EMAIL_REPLY

    s = smtplib.SMTP(SMTP_SERVER)
    s.starttls()
    s.login(EMAIL_ACCOUNT, EMAIL_PSWD)
    s.send_message(msg)

    if csv_file and p:
        csv_msg = EmailMessage()
        csv_msg['Subject'] = 'New Registration ' + p.name if p else ""
        csv_msg['From'] = EMAIL_ADDRESS
        csv_msg['To'] = p._email # FIXME this is wrong!!! needs to be EMAIL_REPLy

        with open(csv_file, 'rb') as fp:
            file_data = fp.read()
            csv_msg.add_attachment(file_data, maintype='text', subtype="comma-separated-values",
                                   filename=os.path.basename(csv_file))
        s.send_message(csv_msg)
    s.quit()


if __name__ == "__main__":
    M = imaplib.IMAP4_SSL(IMAP_SERVER)
    try:
        rv, data = M.login(EMAIL_ACCOUNT, EMAIL_PSWD)
    except imaplib.IMAP4.error:
        print ("LOGIN FAILED!!! ")
        sys.exit(1)
    # rv, mailboxes = M.list()
    # if rv == 'OK':
    #    print("Mailboxes:")
    #    print(mailboxes)
    rv, data = M.select(EMAIL_FOLDER)
    if rv != 'OK':
        print("ERROR: Unable to open mailbox folder ", rv)

    new_participations = process_registrations(M)
    for p in new_participations:
        print(p.name)
        csv_file = p.save_to_csv()
        send_confirmation(p, csv_file)
    M.close()
    M.logout()