import hashlib
import json
import os
import random
import string
import smtplib
import time
from datetime import datetime

import requests
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from dateutil.relativedelta import relativedelta
from flask import Flask, render_template, redirect, url_for, request, session, jsonify
from flask_sqlalchemy import SQLAlchemy
from werkzeug.utils import secure_filename

from config_hackWPI import api_keys, SERVER_PORT, WAITLIST_LIMIT, HACKATHON_TIME, ALLOWED_EXTENSIONS

app = Flask(__name__)
app.config.from_pyfile('config.py')

db = SQLAlchemy(app)





class Hacker(db.Model):
    __tablename__ = 'hackers'

    mlh_id = db.Column(db.Integer, primary_key=True)
    registration_time = db.Column(db.Integer)
    checked_in = db.Column(db.Boolean)
    waitlisted = db.Column(db.Boolean)
    admin = db.Column(db.Boolean)
    first_name = db.Column(db.String(100))
    last_name = db.Column(db.String(100))
    email = db.Column(db.String(100))


class AutoPromoteKeys(db.Model):
    __tablename__ = 'AutoPromoteKeys'

    id = db.Column(db.Integer, primary_key=True)
    key = db.Column(db.String(4096))
    val = db.Column(db.String(4096))

@app.errorhandler(413)
def filesize_too_big(erro):
    print("Someone tried to send something too big")
    return "That file was too big, please go back and try a smaller resume pdf"

@app.errorhandler(500)
def server_error():
    print("There was a server error... If you're having trouble registering, please email hack@wpi.edu with your details and what you did to break our site :P")

@app.route('/')
def root():
    print("Someone visited!.")
    return render_template('index.html')

@app.route('/resumepost', methods=['POST'])
def resumepost():
    """A last minute hack to let people post their resume after they've already registered"""
    if request.method == 'POST':
        if 'resume' not in request.files:
            return "You tried to submit a resume with no file"

        resume = request.files['resume']
        if resume.filename == '':
            return "You tried to submit a resume with no file"

        if resume and not allowed_file(resume.filename):
            return jsonify(
                {'status': 'error', 'action': 'register',
                 'more_info': 'Invalid file type... Accepted types are txt pdf doc docx and rtf...'})

        if resume and allowed_file(resume.filename):
            # Good file!
            filename = session['mymlh']['first_name'].lower() + '_' + session['mymlh']['last_name'].lower() + '_' + str(
                session['mymlh']['id']) + '.' + resume.filename.split('.')[-1].lower()
            filename = secure_filename(filename)
            resume.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            return "Resume uploaded!"
    return "Something went wrong. If this keeps happening, slack message @bkayastha"


@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        # Register a hacker...
        if is_logged_in() and db.session.query(
                db.exists().where(Hacker.mlh_id == session['mymlh']['id'])).scalar():
            # Already logged in, take them to dashboard
            return redirect(url_for('dashboard'))

        if request.args.get('code') is None:
            # Get info from MyMLH
            return redirect(
                'https://my.mlh.io/oauth/authorize?client_id=' + api_keys['mlh']['client_id'] + '&redirect_uri=' +
                api_keys['mlh'][
                    'callback'] + '&response_type=code&scope=email+phone_number+demographics+birthday+education+event')

        if is_logged_in():
            return render_template('register.html', name=session['mymlh']['first_name'])

        code = request.args.get('code')
        oauth_redirect = requests.post(
            'https://my.mlh.io/oauth/token?client_id=' + api_keys['mlh']['client_id'] + '&client_secret=' +
            api_keys['mlh'][
                'secret'] + '&code=' + code + '&redirect_uri=' + api_keys['mlh'][
                'callback'] + '&grant_type=authorization_code')

        if oauth_redirect.status_code == 200:
            access_token = json.loads(oauth_redirect.text)['access_token']
            user_info_request = requests.get('https://my.mlh.io/api/v2/user.json?access_token=' + access_token)
            if user_info_request.status_code == 200:
                user = json.loads(user_info_request.text)['data']
                session['mymlh'] = user
                if db.session.query(db.exists().where(Hacker.mlh_id == user['id'])).scalar():
                    # User already exists in db, log them in
                    return redirect(url_for('dashboard'))

                return render_template('register.html', name=user['first_name'])

        return redirect(url_for('register'))


    if request.method == 'POST':
        if not is_logged_in() or db.session.query(
                db.exists().where(Hacker.mlh_id == session['mymlh']['id'])).scalar():
            # Request flow == messed up somehow, restart them
            return redirect(url_for('register'))

        if 'resume' not in request.files:
            # No file?
            return redirect(url_for('register'))

        resume = request.files['resume']
        if resume.filename == '':
            resume = False
            # No file selected
            #return redirect(url_for('register'))

        if resume and not allowed_file(resume.filename):
            resume = False
            #return jsonify(
            #    {'status': 'error', 'action': 'register',
            #     'more_info': 'Invalid file type... Accepted types are txt pdf doc docx and rtf...'})

        if resume and allowed_file(resume.filename):
            # Good file!
            filename = session['mymlh']['first_name'].lower() + '_' + session['mymlh']['last_name'].lower() + '_' + str(
                session['mymlh']['id']) + '.' + resume.filename.split('.')[-1].lower()
            filename = secure_filename(filename)
            resume.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))

        # Determine if hacker should be placed on waitlist
        waitlist = False
        if db.session.query(Hacker).count() + 1 > WAITLIST_LIMIT:
            print(session['mymlh']['first_name'] + " put on waitlist.")
            waitlist = True
        else:
            print(session['mymlh']['first_name'] + " put on registered.")

        first_name = session['mymlh']['first_name']
        last_name = session['mymlh']['last_name']
        email = session['mymlh']['email']

        # Add the user to the database
        print(Hacker(mlh_id=session['mymlh']['id'], registration_time=int(time.time()),
                   checked_in=False, waitlisted=waitlist, admin=False))
        db.session.add(
            Hacker(mlh_id=session['mymlh']['id'], registration_time=int(time.time()),
                   checked_in=False, waitlisted=waitlist, admin=False,
                   first_name=first_name, last_name=last_name, email=email))
        db.session.commit()
        print(session['mymlh']['first_name'] + " put on database successfully.")

        # Send a welcome email
        msg = 'Dear ' + session['mymlh']['first_name'] + '\n\n'
        msg += 'Thanks for applying to Hack@WPI!\n'
        if waitlist:
            msg += 'Sorry! We have hit our registration capacity. You have been placed on the waitlist.\n'
            msg += 'We will let you know if space opens up.\n'
        else:
            msg += 'You are fully registered! We will send you more info closer to the hackathon.\n'
        send_email(session['mymlh']['email'], 'Hack@WPI - Thanks for applying', msg)

        # Finally, send them to their dashboard
        return redirect(url_for('dashboard'))


@app.route('/admin', methods=['GET'])
def admin():
    # Displays total registration information...
    # As Firebase could not be used with MyMLH, use PubNub to simulate the realtime database...

    if not is_admin():
        return redirect(url_for('register'))

    waitlist_count = 0
    total_count = 0
    check_in_count = 0
    shirt_count = {'xxs': 0, 'xs': 0, 's': 0, 'm': 0, 'l': 0, 'xl': 0, 'xxl': 0}
    male_count = 0
    female_count = 0
    schools = {}
    majors = {}

    mlh_info = get_mlh_users()

    hackers = []

    result = db.session.query(Hacker)

    for hacker in mlh_info:
        obj = result.filter(Hacker.mlh_id == hacker['id']).one_or_none()

        if obj is None:
            continue

        if obj.waitlisted:
            waitlist_count += 1

        if obj.checked_in:
            check_in_count += 1

        if hacker['gender'] == 'Male':
            male_count += 1
        else:
            female_count += 1

        total_count += 1

        if hacker['school']['name'] not in schools:
            schools[hacker['school']['name']] = 1
        else:
            schools[hacker['school']['name']] += 1

        if hacker['major'] not in majors:
            majors[hacker['major']] = 1
        else:
            majors[hacker['major']] += 1

        shirt_count[hacker['shirt_size'].split(' - ')[1].lower()] += 1

        hackers.append({
            'checked_in': obj.checked_in,
            'waitlisted': obj.waitlisted,
            'admin': obj.admin,
            'registration_time': obj.registration_time,
            'id': hacker['id'],
            'email': hacker['email'],
            'first_name': hacker['first_name'],
            'last_name': hacker['last_name'],
            'phone_number': hacker['phone_number'],
            'dietary_restrictions': hacker['dietary_restrictions'],
            'special_needs': hacker['special_needs'],
            'school': hacker['school']
        })

    return render_template('admin.html', hackers=hackers, total_count=total_count, waitlist_count=waitlist_count,
                           check_in_count=check_in_count, shirt_count=shirt_count, female_count=female_count,
                           male_count=male_count, schools=schools, majors=majors,
                           mlh_url='https://my.mlh.io/api/v2/users.json?client_id=' + api_keys['mlh'][
                               'client_id'] + '&secret=' + api_keys['mlh'][
                                       'secret'])


@app.route('/change_admin', methods=['GET'])
def change_admin():
    # Promote or drop a given hacker to/from admin status...
    if not is_admin():
        return jsonify({'status': 'error', 'action': 'modify_permissions',
                        'more_info': 'You do not have permissions to perform this action...'})

    if request.args.get('mlh_id') is None or request.args.get('action') is None:
        return jsonify({'status': 'error', 'action': 'change_admin', 'more_info': 'Missing required field...'})

    valid_actions = ['promote', 'demote']
    if request.args.get('action') not in valid_actions:
        return jsonify({'status': 'error', 'action': 'change_admin', 'more_info': 'Invalid action...'})

    if request.args.get('action') == 'promote':
        db.session.query(Hacker).filter(Hacker.mlh_id == request.args.get('mlh_id')).update({'admin': True})
    elif request.args.get('action') == 'demote':
        db.session.query(Hacker).filter(Hacker.mlh_id == request.args.get('mlh_id')).update({'admin': False})
    db.session.commit()


    return jsonify({'status': 'success', 'action': 'change_admin:' + request.args.get('action'), 'more_info': '',
                    'id': request.args.get('mlh_id')})


@app.route('/check_in', methods=['GET'])
def check_in():
    # Check in a hacker...
    if not is_admin():
        return jsonify({'status': 'error', 'action': 'check_in',
                        'more_info': 'You do not have permissions to perform this action...'})

    if request.args.get('mlh_id') is None:
        return jsonify({'status': 'error', 'action': 'check_in', 'more_info': 'Missing required field...'})

    # See if hacker was already checked in...
    checked_in = db.session.query(Hacker.checked_in).filter(
        Hacker.mlh_id == request.args.get('mlh_id')).one_or_none()

    print(db.session.query(Hacker.checked_in).filter(Hacker.mlh_id == request.args.get('mlh_id')))
    print(checked_in)
    if checked_in and checked_in[0]:
        return jsonify({'status': 'error', 'action': 'check_in', 'more_info': 'Hacker already checked in!'})

    # Update db...
    db.session.query(Hacker).filter(Hacker.mlh_id == request.args.get('mlh_id')).update({'checked_in': True})
    db.session.commit()

    mlh_info = get_mlh_user(request.args.get('mlh_id'))

    # Send a welcome email...
    msg = 'Dear ' + mlh_info['first_name'] + ',\n\n'
    msg += 'Thanks for checking in!\n'
    msg += 'We will start shortly, please check your dashboard for updates!\n'
    msg += 'Here are the slides with all the info: https://goo.gl/mGLDem\n'
    send_email(mlh_info['email'], 'HackWPI - Thanks for checking in', msg)

    return jsonify(
        {'status': 'success', 'action': 'check_in', 'more_info': '', 'id': request.args.get('mlh_id')})


@app.route('/drop', methods=['GET'])
def drop():
    # Drop a hacker's registration...
    if request.args.get('mlh_id') is None:
        return jsonify({'status': 'error', 'action': 'drop', 'more_info': 'Missing required field...'})

    if not is_admin() and not is_self(request.args.get('mlh_id')):
        return jsonify({'status': 'error', 'action': 'drop',
                        'more_info': 'You do not have permissions to perform this action...'})

    row = db.session.query(Hacker.checked_in, Hacker.waitlisted).filter(
        Hacker.mlh_id == request.args.get('mlh_id')).one_or_none()

    if row is None:
        return jsonify({'status': 'error', 'action': 'drop',
                        'more_info': 'Could not find hacker...'})

    (checked_in, waitlisted) = row

    if checked_in:
        return jsonify({'status': 'error', 'action': 'drop', 'more_info': 'Cannot drop, already checked in...'})

    mlh_info = get_mlh_user(request.args.get('mlh_id'))
    print(mlh_info['first_name'] + " trying to drop.")


    # Delete from db...
    row = db.session.query(Hacker).filter(Hacker.mlh_id == request.args.get('mlh_id')).first()
    db.session.delete(row)
    db.session.commit()

    # Delete resume...
    for ext in ALLOWED_EXTENSIONS:
        filename = mlh_info['first_name'].lower() + '_' + mlh_info['last_name'].lower() + '_' + request.args.get(
            'mlh_id') + '.' + ext
        try:
            os.remove(app.config['UPLOAD_FOLDER'] + '/' + filename)
        except OSError:
            pass

    # Send a goodbye email...
    msg = 'Dear ' + mlh_info['first_name'] + ',\n\n'
    msg += 'Your application was dropped, sorry to see you go.\n If this was a mistake, you can re-register by going to hack.wpi.edu/register'
    send_email(mlh_info['email'], 'Hack@WPI - Application Dropped', msg)


    if is_self(request.args.get('mlh_id')):
        session.clear()

    print(mlh_info['first_name'] + " dropped successfully.")
    return jsonify({'status': 'success', 'action': 'drop', 'more_info': '', 'id': request.args.get('mlh_id')})


@app.route('/promote_from_waitlist', methods=['GET'])
def promote_from_waitlist():
    print("Time for promotion!")
    # Promote a hacker from the waitlist...
    if request.args.get('mlh_id') is None:
        return jsonify({'status': 'error', 'action': 'promote_from_waitlist', 'more_info': 'Missing required field...'})

    (key, val) = get_auto_promote_keys()

    if request.args.get(key) is None:
        if not is_admin():
            return jsonify({'status': 'error', 'action': 'promote_from_waitlist',
                            'more_info': 'You do not have permissions to perform this action...'})
    else:
        if request.args.get(key) != val:
            return jsonify({'status': 'error', 'action': 'promote_from_waitlist',
                            'more_info': 'Invalid auto promote keys...'})

    (checked_in, waitlisted) = db.session.query(Hacker.checked_in, Hacker.waitlisted).filter(
        Hacker.mlh_id == request.args.get('mlh_id')).one_or_none()

    if checked_in:
        return jsonify(
            {'status': 'error', 'action': 'promote_from_waitlist',
             'more_info': 'Cannot promote, already checked in...'})

    if not waitlisted:
        return jsonify(
            {'status': 'error', 'action': 'promote_from_waitlist',
             'more_info': 'Cannot promote, user is not waitlisted...'})

    # Update db...
    db.session.query(Hacker).filter(Hacker.mlh_id == request.args.get('mlh_id')).update({'waitlisted': False})
    db.session.commit()

    mlh_info = get_mlh_user(request.args.get('mlh_id'))

    # Send a welcome email...
    msg = 'Dear ' + mlh_info['first_name'] + ',\n\n'
    msg += 'You are off the waitlist!\n'
    msg += 'Room has opened up, and you are now welcome to come, we look forward to seeing you!\n'
    msg += 'If you cannot make it, please remove yourself at hack.wpi.edu\dashboard.\n'
    send_email(mlh_info['email'], "Hack@WPI - You're off the Waitlist!", msg)


    print(mlh_info['first_name'] + "is off the waitlist!")

    return jsonify(
        {'status': 'success', 'action': 'promote_from_waitlist', 'more_info': '', 'id': request.args.get('mlh_id')})


@app.route('/dashboard', methods=['GET'])
def dashboard():
    # Display's a hacker's options...
    if not is_logged_in():
        return redirect(url_for('register'))

    return render_template('dashboard.html', name=session['mymlh']['first_name'], id=session['mymlh']['id'],
                           admin=is_admin())


def is_logged_in():
    if session is None:
        return False
    if 'mymlh' not in session:
        return False
    return True


def is_admin():
    if not is_logged_in():
        return False
    user_admin = db.session.query(Hacker.admin).filter(Hacker.mlh_id == session['mymlh']['id']).one_or_none()
    if user_admin is None:
        return False
    if not user_admin[0]:
        return False
    return True


def is_self(mlh_id):
    mlh_id = int(mlh_id)
    if not is_logged_in():
        return False
    if session['mymlh']['id'] != mlh_id:
        return False
    return True


def send_email(to, subject, body):
    print("Email sent to: " + to)
    body += '\nPlease let your friends know about the event as well!.\n'
    body += 'To update your status, you can go to hack.wpi.edu/dashboard\n'
    body += '\nAll the best!\nThe HackWPI Team\nhttps://twitter.com/hackwpi?lang=en'

    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.starttls()
    sender = api_keys['smtp_email']['user']
    server.login(sender, api_keys['smtp_email']['pass'])

    msg = _create_MIMEMultipart(subject, sender, to, body)

    server.send_message(msg)
    print("Sucess! (Email to " + to)


def _create_MIMEMultipart(subject, sender, to, body):
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = sender
    if type(to) == list:
        msg['To'] = ", ".join(to)
    else:
        msg['To'] = to
    msg.attach(MIMEText(body, 'plain'))
    return msg


def get_mlh_user(mlh_id):
    if not isinstance(mlh_id, int):
        mlh_id = int(mlh_id)
    req = requests.get(
        'https://my.mlh.io/api/v2/users.json?client_id=' + api_keys['mlh']['client_id'] + '&secret=' + api_keys['mlh'][
            'secret'])
    if req.status_code == 200:
        hackers = req.json()['data']
        for hacker in hackers:
            if hacker['id'] == mlh_id:
                return hacker


def get_mlh_users():
    req = requests.get(
        'https://my.mlh.io/api/v2/users.json?client_id=' + api_keys['mlh']['client_id'] + '&secret=' + api_keys['mlh'][
            'secret'])
    if req.status_code == 200:
        return req.json()['data']


def gen_new_auto_promote_keys(n=50):
    key = new_key(n)
    val = new_key(n)
    db.session.add(AutoPromoteKeys(key=key, val=val))
    db.session.commit()
    return (key, val)


def get_auto_promote_keys():
    row = db.session.query(AutoPromoteKeys).one_or_none()
    if row is not None:
        db.session.delete(row)
        db.session.commit()
        return (row.key, row.val)
    else:
        return ('', '')


def new_key(n):
    return ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(n))


def allowed_file(filename):
    return '.' in filename and \
           filename.split('.')[-1].lower() in ALLOWED_EXTENSIONS


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=SERVER_PORT, threaded=True)
