from datetime import timedelta
from os.path import exists

from cryptography.exceptions import InvalidSignature
from flask import Flask, request, render_template
from flask_mail import Mail
from flask_caching import Cache
from pymemcache import Client

from security.token import *
from server.server import *
from server.request import *

app = Flask(__name__)

app.config['CACHE_TYPE'] = 'MemcachedCache'
app.config['CACHE_MEMCACHED_SERVERS'] = Client("memcached.local")

cache = Cache(app)

from security.codes import *

app.config['MAIL_SERVER'] = 'in-v3.mailjet.com'
app.config['MAIL_DEFAULT_SENDER'] = 'no-reply@matchinit.st.inf.tu-dresden.de'
app.config['MAIL_USERNAME'] = 'e0e50dcbef8614ca38b743390cc71678'
app.config['MAIL_PASSWORD'] = 'f246887ee2b754983d64602aa79c7ca9'
app.config['MAIL_USE_TLS'] = 'True'
app.config['MAIL_PORT'] = '587'

mail = Mail(app)

from mail.send_mail import send_mail


def make_response(status, message, result=None):
    data = {"message": message}
    if result:
        data["result"] = result
    response = app.response_class(
        response=json.dumps(data, indent=4),
        status=status,
        mimetype='application/json',
    )
    return response


# Method: Get
@app.route('/get_universities', methods=['GET'])
def get_universities():
    try:
        return make_response(200, "success", get_servers_list())

    except TypeError:
        return make_response(400, "bad request")


# Method: Get
@app.route('/add_code', methods=['GET', 'POST'])
def add_code():
    if not exists("security/enable_manual_codes"):
        return ""
    if request.method == 'GET':
        return render_template('codes.html', submitted=False)
    else:
        email = request.form["email"]
        code = request.form["code"]
        code = set_code(email, code, "register")
        return render_template('codes.html', submitted=True, code=code)


# Method: Post
# Content:
# {
#   "email": <email>
# }
@app.route('/send_verify_email', methods=['POST'])
def send_verify_email():
    try:
        load = request.get_json(silent=False)
        email = load["email"].lower()
        server = get_server(email)
        if send_check_email(email, server):
            raise EmailAlreadyInUse
        code = new_code(email, "register")
        send_mail(email, code, "register_code_email.html")
        return make_response(200, "email sent")

    except TypeError:
        return make_response(400, "bad request")

    except NoServerAssociatedError:
        return make_response(406, "please use your TU-Dresden email to register")

    except KeyError:
        return make_response(400, "invalid token structure")

    except EmailAlreadyInUse:
        return make_response(403, "email already in use")

    except UnableToAccessGateway:
        return make_response(502, "unable to access gateway")


# Method: Post
# Content:
# {
#   "email": <email>
# }
@app.route('/send_forgot_password_email', methods=['POST'])
def send_forgot_password_email():
    try:
        load = request.get_json(silent=False)
        email = load["email"].lower()
        server = get_server(email)
        if not send_check_email(email, server):
            raise UserDoesNotExist
        code = new_code(email, "password_reset")
        send_mail(email, code, "password_reset_code_email.html")
        return make_response(200, "email sent")

    except UserDoesNotExist:
        return make_response(200, "email sent")

    except TypeError:
        return make_response(400, "bad request")

    except NoServerAssociatedError:
        return make_response(406, "not a valid email address")

    except KeyError:
        return make_response(400, "invalid token structure")

    except UnableToAccessGateway:
        return make_response(502, "unable to access gateway")


# Method: Post
# Content:
# {
#   "email": <email>,
#   "code": <code>
# }
@app.route('/verify_email', methods=['POST'])
def verify_email():
    try:
        load = request.get_json(silent=False)
        email = load["email"].lower()
        code = load["code"]
        purpose = verify_code(email, code)
        email_token = Token()
        email_token.__dict__ = {"email": email,
                                "verified": True,
                                "date": datetime.utcnow().isoformat(),
                                "purpose": purpose}

        result = {"email_token": sign_token(email_token)}

        return make_response(200, "success", result)

    except TypeError:
        return make_response(400, "bad request")

    except KeyError:
        return make_response(400, "invalid token structure")

    except WrongCodeError:
        return make_response(403, "Wrong Code")

    except UnableToAccessGateway:
        return make_response(502, "unable to access gateway")


# Method: Post
# Content:
# {
#   "email_token": <email_token>,
#   "username": <username>,
#   "display_name": <display_name>,
#   "password": <password>
# }
@app.route('/register', methods=['POST'])
def register():
    try:
        load = request.get_json(silent=False)
        email_token = verify_token(load["email_token"]["token"], load["email_token"]["signature"])
        if not email_token["verified"] or email_token["purpose"] != "register":
            raise WrongTokenError
        if datetime.fromisoformat(email_token["date"]) + timedelta(minutes=30) < datetime.utcnow():
            raise TokenExpiredError
        email = email_token["email"]
        username = load["username"]
        display_name = load["display_name"]
        password = load["password"]
        server = get_server(email)
        user_id, login_id = send_register(username, display_name, password, email, server)
        renew_token, access_token = generate_tokens(user_id, login_id, server)
        result = {"server_url": SERVER[server]["server_url"],
                  "renew_token": sign_token(renew_token),
                  "access_token": sign_token(access_token)}
        return make_response(200, "success", result)

    except UnableToRegister:
        return make_response(403, "you are not able to register right now")

    except WrongTokenError:
        return make_response(403, "you submitted the wrong token")

    except TokenExpiredError:

        return make_response(403, "your token is expired please restart the registration process")

    except UserAlreadyExists:
        return make_response(406, "the email address is already in use")

    except UsernameTaken:
        return make_response(406, "the username is already taken")

    except UserLimitReached:
        return make_response(406, "we have reached the user limit for our beta test")

    except TypeError:
        return make_response(400, "bad request")

    except KeyError:
        return make_response(400, "invalid token structure")

    except UnableToAccessGateway:
        return make_response(502, "unable to access gateway")


# Method: Post
# Content:
# {
#   "email_token": <email_token>,
#   "new_password": <new_password>
# }
@app.route('/new_password', methods=['POST'])
def new_password():
    try:
        load = request.get_json(silent=False)
        email_token = verify_token(load["email_token"]["token"], load["email_token"]["signature"])
        if not email_token["verified"] or email_token["purpose"] != "password_reset":
            raise WrongTokenError
        if datetime.fromisoformat(email_token["date"]) + timedelta(minutes=30) < datetime.utcnow():
            raise TokenExpiredError
        email = email_token["email"]
        password = load["new_password"]
        send_set_new_password(email, password, get_server(email))
        return make_response(200, "new password set")

    except CouldNotSetNewPassword:
        return make_response(502, "there was an error when setting your new password")

    except WrongTokenError:
        return make_response(403, "you submitted the wrong token")

    except TokenExpiredError:
        return make_response(403, "your token is expired please restart the password reset process")

    except TypeError:
        return make_response(400, "bad request")

    except KeyError:
        return make_response(400, "invalid token structure")

    except UnableToAccessGateway:
        return make_response(502, "unable to access gateway")


# Method: Post
# Content:
# {
#   "email": <email>,
#   "password": <password>
# }
@app.route('/login', methods=['POST'])
def login():
    try:
        load = request.get_json(silent=False)
        email = load["email"].lower()
        password = load["password"]
        server = get_server(email)
        user_id, login_id = send_login(email, password, server)
        renew_token, access_token = generate_tokens(user_id, login_id, server)
        result = {'server_url': SERVER[server]["server_url"],
                  'renew_token': sign_token(renew_token),
                  'access_token': sign_token(access_token)}
        return make_response(200, "success", result)

    except TypeError:
        return make_response(400, "bad request")

    except NoServerAssociatedError:
        return make_response(406, "email address is not supported")

    except UnableToAccessGateway:
        return make_response(502, "unable to access gateway")

    except LoginFailed:
        return make_response(403, "login failed")

    except UserBanned as ban:
        if ban.banned_for == "permanent":
            return make_response(403, "you are permanently banned")
        else:
            return make_response(403, f"you are banned for {ban.banned_for}")


# Method: Post
# Content:
# {
#   "renew_token": <renew_token>
# }
@app.route('/renew', methods=['POST'])
def renew():
    try:
        load = request.get_json(silent=False)
        renew_token = verify_renew(load["renew_token"]["token"], load["renew_token"]["signature"])
        send_check_login_id(renew_token.user_id, renew_token.login_id, renew_token.server)
        access_token = generate_access_token(renew_token)
        result = {"access_token": sign_token(access_token)}

        return make_response(200, "success", result)

    except TypeError:
        return make_response(400, "bad request")

    except InvalidSignature:
        return make_response(403, "invalid signature")

    except KeyError:
        return make_response(400, "invalid token structure")

    except LoginIDNotValid:
        return make_response(403, "renew token not valid anymore")

    except UnableToAccessGateway:
        return make_response(502, "unable to access gateway")
