import os
import errno
from html import escape
import teampy
import pandas as pd
from datetime import date
from teampy import Questionaire, SolutionDocument, Students, Teams, Solution, Result, Teampy, RATContext, tell
from colorama import init, Fore, Style
import click
import shutil
import time

# sending email
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
from smtplib import SMTPHeloError, SMTPRecipientsRefused, SMTPNotSupportedError, SMTPAuthenticationError, SMTPSenderRefused, SMTPDataError, SMTPException
import getpass
import progressbar
from latex import build_pdf

def print_teampy():
    print('')
    print(' _                                   ')
    print('| |_ ___  __ _ _ __ ___  _ __  _   _ ')
    print('| __/ _ \/ _` | \'_ ` _ \| \'_ \| | | |')
    print('| ||  __/ (_| | | | | | | |_) | |_| |')
    print(' \__\___|\__,_|_| |_| |_| .__/ \__, |')
    print('                        |_|    |___/ {}'.format(teampy.__version__))
    print('')

def copy_figures(rat_directory):
    # create the figures directory
    figures = os.path.join(rat_directory, 'figures')
    if not os.path.exists(figures):
        os.makedirs(figures)
    for filename in ['rat-box.pdf', 'scratch.pdf']:
        file = os.path.join(os.path.dirname(__file__), filename)
        shutil.copy(file, figures)
        
def make_sure_path_exists(path):
    """
    Ensures that all directories for a given path exist.
    """
    try:
        os.makedirs(path)
    except OSError as exception:
        if exception.errno != errno.EEXIST:
            raise

def rat_create():
    """
    Create a template file for a RAT
    """
    print_formatted_text(HTML('<darkgray>Create a new template file for a RAT.</darkgray>'))

    from prompt_toolkit.shortcuts import input_dialog

    # create directory
    #number = int(prompt('Number of the RAT: ', validator=NumberValidator(), key_bindings=bindings))
    number = prompt('Number of the RAT: ', validator=NumberValidator(), key_bindings=bindings)
    title = prompt(' Title of the RAT: ')
    rat_id = 'rat-' + str(number).zfill(2)
    # create template file
    directory = os.path.join(os.getcwd(), rat_id)
    if not os.path.exists(directory):
        os.makedirs(directory)
    file = os.path.join(directory, 'questions.md')


def _load_rat_file(file_input):
    content = file_input.readlines()
    file_input.close()
    questionaire = Questionaire()
    code, message = questionaire._parse(content)
    if code == 1: # warnig
        print(Fore.YELLOW + message + Style.RESET_ALL)
        return None
    elif code == 2: # error
        print(Fore.RED + message + Style.RESET_ALL)
        return None
    return questionaire


def write_latex(latex, file_path, old_latex=False):
    tex_file_name = os.path.splitext(os.path.basename(file_path))[0] + '.tex'
    tex_file_path = os.path.join(os.path.dirname(file_path), tex_file_name)
    # TODO check if file already exists
    with open(tex_file_path, "w", encoding='utf-8') as file:
        file.write(latex)
    if old_latex:
        # copy also ratbox figures
        copy_figures(os.path.dirname(file_path))
    tell('Write LaTeX into file {}.'.format(tex_file_path))


def rat_check(file_input, file_path):
    """
    Read the RAT questionaire, check for consistency, print it as tex document,
    but without solutions or fo specific students or teams.
    """
    questionaire = _load_rat_file(file_input)
    if questionaire is None:
        return

    # TODO does not detect 4 fake answers, or several true answers, improve parser
    tell('The RAT has {} questions.'.format(len(questionaire.questions)))
    for question in questionaire.questions:
        fake = len(question.fake)
        if fake != 3:
            print(Fore.RED + 'Question {} has only {} fake answers. Must be 3.'.format(question.number, fake) + Style.RESET_ALL)
            return
        if question.true is None:
            print(Fore.RED + 'Question {} has no true answer. The first answer alternative must be the true one.'.format(question.number) + Style.RESET_ALL)
            return
    tell('The rat looks okay.')
    latex = questionaire.write_latex()
    write_latex(latex, file_path)

def parallel_file_path(file_path, alternative_extension):
    head, tail = os.path.split(file_path)
    tail_base = tail.split('.')[0]
    return os.path.join(os.path.dirname(file_path), '{}{}'.format(tail_base, alternative_extension))

def rat_print(file_input, file_path, team_solution, old_latex=False, pdf=True):
    """
    Create a document with RATs for all students and all teams.
    """
    t = Teampy()
    questionaire = _load_rat_file(file_input)
    if questionaire is None:
        return

    if team_solution in t.scratchcards:
        scratch_card_id = team_solution
        team_solution = t.scratchcards[team_solution]
        # TODO check if solution matches requirement from questionaire
        tell('Found scratch card {} with solution {}'.format(scratch_card_id, team_solution.to_string()))
    else:
        # TODO check if solution matches requirement from questionaire
        team_solution = Solution.create_solution_from_string(team_solution)

    # TODO check if team solution has correct number
    # TODO look for already existing solution document, load and update it

    # create a solution file
    sd = SolutionDocument()
    sd.create_solution_document(t.teams, t.students, questionaire, team_solution)
    solutions_file_path = os.path.join(os.path.dirname(file_path), 'solutions.teampy')
    sd.store(solutions_file_path)
    tell('Write solutions into file {}.'.format(solutions_file_path))

    latex = questionaire.write_latex(sd, t.teams, t.students, old_latex)
    if pdf:
        pdf_file_name = os.path.splitext(os.path.basename(file_path))[0] + '.pdf'
        tell('Creating PDF...')
        # empty directory for tex reasons...
        current_dir = os.path.abspath(os.path.dirname(file_path))
        pdf = build_pdf(latex, texinputs=[current_dir, ''])
        pdf.save_to(pdf_file_name)
        tell('Done!')
    else:
        write_latex(latex, file_path, old_latex=old_latex)
        tell('Done!')
        print('   As a next step, print the LaTeX document and do the RAT.')


def rat_grade(file_input, file_path):
    """
    Evaluate the results of a RAT.
    """
    teampy = Teampy()
    rat = RATContext(teampy)

    # read in the questionaire
    questionaire = _load_rat_file(click.open_file(rat.questionaire_file, encoding='utf-8'))
    if questionaire is None:
        tell('Could not read questions. Aborting.', 'error')
        return
    # read in the solutions file
    solutions = SolutionDocument()
    solutions.load(rat.solutions_file, teampy.students, teampy.teams)

    result = Result(teampy.students, teampy.teams, questionaire, solutions)
    student_results_count, team_results_count = result.load_results(file_input)

    if (student_results_count + team_results_count) == 0:
        tell('The results file does not contain any results.', 'warning')
        return

    results_file_path = parallel_file_path(file_path, '.xlsx')
    result.store_results(results_file_path)
    results_file_path = parallel_file_path(file_path, '.html')
    result.store_results_html(results_file_path)
    result.stats(filename=parallel_file_path(file_path, '_stats.html'))

def escape_html_entities(string):
    return string.encode('ascii', 'xmlcharrefreplace').decode('UTF-8')

def create_message(student_id, row, result, teampy):
    msg = MIMEMultipart()
    msg['From'] = teampy.smtp_settings['from']
    msg['To'] = row['email']
    # TODO here the course code would be nice to have
    msg['Subject'] = 'RAT Feedback'
    firstname = escape_html_entities(row['firstname'])
    lastname = escape_html_entities(row['lastname'])
    student_id = escape_html_entities(student_id)
    html = """\
<html>
  <head></head>
  <body>
    <p>Hi {}!</p>
    <p>Here are the results from the latest RAT:</p>
    <table>
      <tr><td>Name:</td><td>{} {}</td><tr>
      <tr><td>ID:</td><td><code>{}</code></td><tr>
      <tr><td>Team:</td><td><code>{}</code></td><tr>
      <tr><td>RAT:</td><td><code>{}</code></td><tr>
      <tr><td>Date:</td><td><code>{}</code></td><tr>
""".format(firstname, firstname, lastname, student_id, row['team'], result.name, result.date)
    html = html + """\
      <tr><td>Individual Answer:&nbsp;</td><td><code>{}</code></td><tr>
      <tr><td>Correct Solution:</td><td><code>{}</code></td><tr>
      <tr><td>Individual Score:</td><td><code>{}</code> &nbsp; <emph>(counts {}%)</emph></td><tr>
""".format(row['answer_i'], row['correct_i'], row['irat'], row['pi'])
    if row['trat'] is not None: # TODO check what a non-value in Excel looks like here
        html = html + """\
      <tr><td>Team Answer:</td><td><code>{}</code></td><tr>
      <tr><td>Correct Solution:</td><td><code>{}</code></td><tr>
      <tr><td>Team Score:</td><td><code>{}</code> &nbsp; <emph>(counts {}%)</emph></td><tr>
""".format(row['answer_t'], row['correct_t'], row['trat'], row['pt'])
    html = html + """\
      <tr><td>Final Score for this RAT:</td><td><b>{}</b></td><tr>
    </table>
    <p>{}</p>
    <p>This mail was automated, but you can reply to it.</p>
  </body>
</html>
""".format(row['score'], row['comment'])
    msg.attach(MIMEText(html, 'html'))
    return msg


def rat_email(file_input, file_path, testonly):
    """
    Send the results of a RAT to students via email.
    """
    if not file_path.endswith('.xlsx'):
        tell('This command only takes an *.xlsx file as input.', 'error')
        return

    teampy = Teampy()
    
    if teampy.smtp_settings is None:
        tell('Your SMTP settings are not complete. Please check.', 'error')
        return
    
    # TODO give RAT context the directory relative to the file_input
    rat = RATContext(teampy)
    # read in the questionaire
    questionaire = _load_rat_file(click.open_file(rat.questionaire_file, encoding='utf-8'))
    if questionaire is None:
        tell('Could not read questions. Aborting.', 'error')
        return
    # read in the solutions file
    solutions = SolutionDocument()
    solutions.load(rat.solutions_file, teampy.students, teampy.teams)

    # read in the corresponding result file
    results_file_path = parallel_file_path(file_path, '.txt')
    result = Result(teampy.students, teampy.teams, questionaire, solutions)
    result.load_results(click.open_file(results_file_path, encoding='utf-8'))

    # read in the graded file
    df = pd.read_excel(file_path, dtype={'id': str, 'team': str, 'email':str, 'comment':str, 'feedback':str})
    df = df.set_index('id')

    # TODO check that table is consistent
    # TODO abort if there is nothing to send
    # TODO check if all email adresses are valid

    # create all messages first
    messages = {}
    for student_id, row in df.iterrows():
        # TODO check if we need to send email to this student
        if row['feedback'] != 'sent':
            messages[student_id] = create_message(student_id, row, result, teampy)

    # store the messages
    make_sure_path_exists('emails')
    for student_id, message in messages.items():
        html_file_path = os.path.join(os.path.dirname(file_path), 'emails/{}.html'.format(student_id))
        with open(html_file_path, "w") as html_file:
            #html_file.write(message.as_string())
            html_file.write(message.get_payload()[0].get_payload())

    if len(messages)==0:
        tell('There is nothing to send.')
        return
    elif testonly:
        tell('Messages written to email folder, but nothing will be sent due to option --testonly.')
        return
    else:
        tell('Will send messages to {} students. (Press CTRL+C to quit and not send anything. Restart with option --testonly to only store a preview of the emails.)'.format(len(messages)))

    # connect to SMTP server
    print('\n')
    password = getpass.getpass(prompt='Password for the user {} on server {}: '.format(teampy.smtp_settings['from'], teampy.smtp_settings['smtp']))

    #create server
    print('Create client...')
    server = smtplib.SMTP('{}: {}'.format(teampy.smtp_settings['smtp'], teampy.smtp_settings['port']))
    statuses = {}
    try:
        print('Connecting to server...')
        server.starttls()
        # Login Credentials for sending the mail
        server.login(teampy.smtp_settings['from'], password)
        print('Logged in...')
        # send email
        bar = progressbar.ProgressBar(max_value=len(messages), widgets=[progressbar.Percentage(), progressbar.Bar()])
        for student_id, message in messages.items():
            try:
                server.send_message(message)
                time.sleep(0.1)
                statuses[student_id] = 'sent'
            except (SMTPHeloError, SMTPRecipientsRefused, SMTPNotSupportedError, SMTPSenderRefused, SMTPDataError) as e:
                tell('There was an error sending a mail to {}.'.format(student_id))
                statuses[student_id] = 'error'
                print(e)
            bar += 1
    except (SMTPHeloError, SMTPAuthenticationError, SMTPNotSupportedError, SMTPException) as e:
        tell('There was an error connecting to the SMTP server {}'.format(teampy.smtp_settings['smtp']))
    bar.finish()
    server.quit()

    # update the status
    changes = False
    for student_id, status in statuses.items():
        df.at[student_id, 'feedback'] = status
        changes = True
    if changes:
        keep_trying = True
        while keep_trying:
            try:
                df.to_excel(file_path)
                tell('Updated results file with email send status.')
                keep_trying = False
            except PermissionError:
                tell('Can\'t write file {}. Maybe Excel is open? Close it and press enter.'.format(file_path), 'error')
                input("")



@click.group()
@click.version_option(teampy.__version__)
def rat():
    """
    Teampy is a collection of tools for team-based learning.

    The rat command is used to create, print, evaluate and give feedback on
    readiness assurance tests.

    """
    pass

#@teampy.command(help='Handle quizzes.')
#@click.option('--new', 'operation', flag_value='new', default=True, help='Create a new quiz.')
#@click.option('--check', 'operation', flag_value='check', help='Check an existing quiz.')
@rat.command()
def new():
    """
    Create templates for a new RAT.
    """
    print_teampy()
    click.echo('Create a new RAT.')
    # store as UTF-8
    # ask for a title
    # ask for number of questions
    # ask for answer alternatives

    #

    # example question?

    # a complete example document?

@rat.command()
@click.argument('file', type=click.Path(exists=True))
def check(file):
    """
    Check a RAT file for consistency before printing.
    """
    print_teampy()
    rat_check(click.open_file(file, encoding='utf-8'), file)

@rat.command()
@click.argument('file', type=click.Path(exists=True))
def trial(file):
    """
    Print the RAT for a trial run, for instance with a colleague.
    Question sequence is original, but answers are shuffled.
    """
    print_teampy()
    file_path = file
    file = click.open_file(file, encoding='utf-8')
    questionaire = _load_rat_file(file)
    if questionaire is None:
        return
    latex = questionaire.write_trial_latex()
    write_latex(latex, file_path)

@rat.command(name='print')
#@click.argument('file', type=click.File('r'))
@click.argument('file', type=click.Path(exists=True), required=False)
@click.option('--nopdf', default=False, is_flag=True, help='Do not create PDF, just latex source.')
@click.option('--teamsolution', prompt='Team solution', help='Code of the team scratch card or team solution.')
def print_(file, teamsolution, nopdf):
    """
    Print a RAT before class.
    """
    # TODO if no valid team solution code is shown, add prompt that also shows which scratch cards are available
    print_teampy()
    pdf = not nopdf
    if file is None:
        file = os.path.join(os.getcwd(), 'questions.txt')
        if os.path.exists(file):
            if click.confirm('Found file {}. Do you want to print it?'.format(file)):
                rat_print(click.open_file(file, encoding='utf-8'), file, teamsolution, pdf=pdf)
    else:
        rat_print(click.open_file(file, encoding='utf-8'), file, teamsolution, pdf=pdf)


def rat_setup_results_file(result_file):
    with open(result_file, "w", encoding='utf-8') as file:
        file.write(('---\n'
         'name: RAT\n'
         'date: {}\n'
         '---\n'.format(date.today().strftime("%Y-%m-%d"))))


@rat.command()
@click.argument('file', type=click.Path(exists=True), required=False)
def grade(file):
    """
    Evaluate a RAT during class.
    """
    print_teampy()
    if file is None:
        file = os.path.join(os.getcwd(), 'results.txt')
        if os.path.exists(file):
            if click.confirm('Found file {}. Do you want to grade it?'.format(file)):
                rat_grade(click.open_file(file, encoding='utf-8'), file)
        else:
            if click.confirm('No results file found or specified. Should we create an empty one?'.format(file)):
                rat_setup_results_file(file) 
    else:
        rat_grade(click.open_file(file, encoding='utf-8'), file)


@rat.command()
@click.argument('file', type=click.Path(exists=True))
@click.option('--testonly', default=False, is_flag=True, help='Only store HTML messages for testing, do not actually send.')
def email(file, testonly):
    """
    Send feedback to students via email.
    """
    print_teampy()
    rat_email(click.open_file(file, encoding='utf-8'), file, testonly)

if __name__ == "__main__":
    rat()
