#!/usr/bin/env python3

import sys
import io
import imaplib
import os
import email
import email.header
import smtplib
from email.message import EmailMessage
import re
import datetime as dt
import time
import glob

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 = d = dt.datetime.strptime("2020-01-16T00:00:00", "%Y-%m-%dT%H:%M:%S")


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'=C3=BC', 'ü', msg_body)
        msg_body = re.sub(r'=C3=9C', 'Ü', msg_body)
        msg_body = re.sub(r'=C3=A4', 'ä', msg_body)
        msg_body = re.sub(r'=C3=84', 'Ä', msg_body)
        msg_body = re.sub(r'=C3=B6', 'ö', msg_body)
        msg_body = re.sub(r'=C3=96', 'Ö', msg_body)
        msg_body = re.sub(r'=C3=9F', 'ss', 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 participant not student!")
        if (self._food_vegan or self._food_gluten or self._food_vegi) and self._food_normal:
            self._conflicts.append("Possible 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:
            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)

    @property
    def inconsistencies(self):
        return ", ".join(self._conflicts)

    @property
    def message(self):
        return self._message

    def save_to_csv(self, filename=None):
        if not os.path.exists(PARTICIPANTS_FOLDER):
            os.mkdir(PARTICIPANTS_FOLDER)
        if not filename:
            stub = os.path.join(PARTICIPANTS_FOLDER, "".join(self.name.split())) + "*"
            matches = glob.glob(stub)
            if len(matches) > 0:
                filename = os.path.join(PARTICIPANTS_FOLDER, "".join(self.name.split())) + "_%i" % len(matches) + ".csv"
            else:
                filename = os.path.join(PARTICIPANTS_FOLDER, "".join(self.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 io.open(filename, mode="w", encoding="UTF8") as fd:
            fd.write("; ".join(header))
            fd.write("\n")
            fd.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 special preferences" 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" % self.inconsistencies
        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(M, 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("\tFound %i registration mails" % len(msgs), file=log_file)
    for msg_index in msgs:
        participations.append(process_message(M, msg_index))
    return participations


def send_confirmation(p=None, csv_file=None):
    if p is not None:
        assert(isinstance(p, Participation))
    content = "Dear %s,\n\n" % (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 if p else EMAIL_ADDRESS
    msg['Cc'] = EMAIL_ADDRESS
    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
        content = ""
        if p and not p.valid:
            content += "Registration contains inconsistencies!\n\t"
            content += p.inconsistencies
            content += "\n\n"
        if p and len(p.message) > 0:
            content += "Participant left a message:\n %s \n\n" % p.message
        if "_" in csv_file:
            content += "This seems to be a duplication or correction of an existing registration!\n"
        csv_msg.set_content(content)
        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()


def check_for_mails():
    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("\tNew registration by %s" % p.name, file=log_file)
        csv_file = p.save_to_csv()
        send_confirmation(p, csv_file)
    M.close()
    M.logout()


if __name__ == "__main__":
    with open('etho_registration.log', 'a') as log_file:
        while True:
            print("%s: Checking for new registrations mails!" % dt.datetime.now().isoformat(),
                  file=log_file)
            check_for_mails()
            time.sleep(60)