# MIT License

# Copyright (c) 2019 Samuel Hoffman

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import os

from flask import (Blueprint, abort, current_app, flash, redirect,
                   render_template, request, send_from_directory, url_for)
from flask_login import current_user, login_required, login_user, logout_user

from keyserv.auth import Users
from keyserv.forms import AppForm, KeyForm, LoginForm
from keyserv.keymanager import cut_key_unsafe
from keyserv.models import Application, AuditLog, Event, Key, db

frontend = Blueprint("frontend", __name__)


@frontend.route("/favicon.ico")
def favicon():
    return send_from_directory(os.path.join(current_app.root_path, "static"),
                               "favicon.ico",
                               mimetype="image/vnd.microsoft.icon")


@frontend.route("/", methods=["GET", "POST"])
def index():

    form = LoginForm(request.form)

    if request.method == "POST" and form.validate():
        current_app.logger.debug("login form was submitted")
        user = Users.query.filter_by(username=form.username.data).first()
        if user and user.check_password(form.password.data):
            if login_user(user):
                current_app.logger.debug(f"login for {user}")
        else:
            flash("Invalid username or password.", "error")
        return redirect(url_for("frontend.index"))

    return render_template("index.html", form=form, current_user=current_user)


@frontend.route("/logout")
def logout():
    logout_user()
    return redirect(url_for("frontend.index"))


@frontend.route("/keys")
@login_required
def keys():
    return render_template("keys.html", keys=Key.query.all())


@frontend.route("/applications")
@login_required
def apps():
    return render_template("applications.html", apps=Application.query.all())


@frontend.route("/logs")
@login_required
def logs():
    return render_template("logs.html", logs=AuditLog.query.all())


@frontend.route("/modify/key/<int:key_id>", methods=["GET", "POST"])
@login_required
def modify_key(key_id: int):

    key = Key.query.get(key_id)
    if not key:
        abort(404)

    form = KeyForm(request.form)
    form.application.choices = [(app.id, app.name)
                                for app in Application.query.all()]

    if request.method == "POST" and form.validate_on_submit():
        changes = []

        if key.remaining != form.activations.data:
            changes.append(f"activations changed from {key.remaining}"
                           f" to {form.activations.data}")
            key.remaining = form.activations.data
        if key.memo != form.memo.data:
            changes.append(f"memo changed from {key.memo!r}"
                           f" to {form.memo.data!r}")
            key.memo = form.memo.data
        if key.app_id != form.application.data:
            changes.append(f"app changed from {key.app} to"
                           f" {form.application.data}")
            key.application = form.application.data
        if key.enabled != form.active.data:
            changes.append(f"active changed from {key.enabled}"
                           f" to {form.active.data}")
            key.enabled = form.active.data
        if key.hwid != form.hwid.data:
            changes.append(f"hwid changed from {key.hwid!r} to "
                           f"{form.hwid.data!r}")
            key.hwid = form.hwid.data

        AuditLog.from_key(key, f"edited by {current_user.username} "
                          f"({request.remote_addr}):"
                          f" {', '.join(changes)}", Event.KeyModified)

        try:
            db.session.commit()
            flash("Changes successful!")
            return redirect(url_for("frontend.detail_key", key_id=key.id))
        except Exception as error:
            flash(f"Failed to update key: {error}")

    form.application.data = key.app_id
    form.active.data = key.enabled
    form.memo.data = key.memo
    form.activations.data = key.remaining
    form.hwid.data = key.hwid

    return render_template("add_modify.html",
                           header=f"Modify Key {key.id}", form=form)


@frontend.route("/add/key", methods=["GET", "POST"])
@frontend.route("/add/key/<int:app_id>", methods=["GET", "POST"])
@login_required
def add_key(app_id=None):
    form = KeyForm(request.form)
    form.application.choices = [(app.id, app.name)
                                for app in Application.query.all()]

    if app_id:
        form.application.data = app_id

    if request.method == "POST" and form.validate_on_submit():
        try:
            token = cut_key_unsafe(form.activations.data,
                                   form.application.data,
                                   form.active.data, form.memo.data)
            flash(f"Key added! Token: {token}", "success")
        except Exception as error:
            flash(f"Unable to add key: {error}", "error")

    return render_template("add_modify.html", header="Add Key", form=form)


@frontend.route("/add/app", methods=["GET", "POST"])
@login_required
def add_app():
    form = AppForm(request.form)

    if request.method == "POST" and form.validate_on_submit():
        app = Application()
        app.name = form.name.data
        app.support_message = form.support.data

        db.session.add(app)
        try:
            db.session.commit()
            flash("Success!")
        except Exception as error:
            flash(f"Failed to add application: {error}")

    return render_template("add_modify.html",
                           form=form, header="Add Application")


@frontend.route("/modify/app/<int:app_id>", methods=["GET", "POST"])
@login_required
def modify_app(app_id: int):
    app = Application.query.get(app_id)

    if not app:
        abort(404)

    form = AppForm(request.form)
    if request.method == "POST" and form.validate_on_submit():

        app.name = form.name.data
        app.support_message = form.support.data
        try:
            db.session.commit()
            flash("Success.")
            return redirect(url_for("frontend.detail_app", app_id=app.id))
        except Exception as error:
            flash(f"Failed to modify application: {error}", "error")

    form.name.data = app.name
    form.support.data = app.support_message

    return render_template("add_modify.html", form=form)


@frontend.route("/detail/key/<int:key_id>")
@login_required
def detail_key(key_id: int):

    key = Key.query.get(key_id)

    if not key:
        abort(404)

    return render_template("detail_key.html", key=key)


@frontend.route("/detail/app/<int:app_id>")
@login_required
def detail_app(app_id: int):

    app = Application.query.get(app_id)

    if not app:
        abort(404)

    return render_template("detail_app.html", app=app)


@frontend.route("/keys/app/<int:app_id>")
@login_required
def keys_for_app(app_id):

    app = Application.query.get(app_id)

    if not app:
        abort(404)

    return render_template("keys.html", keys=app.keys)


@frontend.route("/keys/deactivate/<int:key_id>")
@login_required
def disable_key(key_id):

    key = Key.query.get(key_id)

    if not key:
        abort(404)

    key.enabled = False
    db.session.commit()

    return redirect(url_for("frontend.detail_key", key_id=key_id))


@frontend.route("/keys/activate/<int:key_id>")
@login_required
def enable_key(key_id):

    key = Key.query.get(key_id)

    if not key:
        abort(404)

    key.enabled = True
    db.session.commit()

    return redirect(url_for("frontend.detail_key", key_id=key_id))