import re
from json import JSONDecodeError

import orjson
from flask import Blueprint, current_app, flash, g, make_response, redirect, render_template, request, session, url_for

from src.config import Configuration
from src.lib.account import (
    attempt_login,
    create_account,
    get_saved_key_import_ids,
    get_saved_keys,
    revoke_saved_keys,
    change_password as db_change_password,
)
from src.lib.notification import count_account_notifications, get_account_notifications, set_notifications_as_seen
from src.lib.security import is_password_compromised
from src.types.account import Account
from src.types.props import SuccessProps
from src.utils.utils import set_query_parameter
from src.utils.decorators import require_login

from .administrator import administrator
from .moderator import moderator
from .types import AccountPageProps, NotificationsProps, ServiceKeysProps

USERNAME_REGEX = re.compile(r"^[a-z0-9_@+.\-]{3,15}$")
account_bp = Blueprint("account", __name__)


@account_bp.get("/account")
def get_account():
    account: Account = g.get("account")
    if not account:
        return redirect(url_for("account.get_login"))

    if Configuration().enable_notifications:
        notifications_count = count_account_notifications(account.id)
    else:
        notifications_count = 0
    props = AccountPageProps(account=account, notifications_count=notifications_count)

    return make_response(render_template("account/home.html", props=props), 200)


@account_bp.get("/account/notifications")
def get_notifications():
    account: Account = g.get("account")
    if not account:
        return redirect(url_for("account.get_login"))

    if Configuration().enable_notifications:
        notifications = get_account_notifications(account.id)
    else:
        notifications = []
    props = NotificationsProps(notifications=notifications)

    seen_notif_ids = [notification.id for notification in notifications if not notification.is_seen]
    set_notifications_as_seen(seen_notif_ids)

    return make_response(render_template("account/notifications.html", props=props), 200)


@account_bp.get("/account/keys")
def get_account_keys():
    account: Account = g.get("account")
    if not account:
        return redirect(url_for("account.get_login"))

    saved_keys = get_saved_keys(account.id)
    props = ServiceKeysProps(service_keys=saved_keys)

    saved_session_key_import_ids = []
    for key in saved_keys:
        saved_session_key_import_ids.append(get_saved_key_import_ids(key.id))

    response = make_response(
        render_template("account/keys.html", props=props, import_ids=saved_session_key_import_ids),
        200,
    )
    response.headers["Cache-Control"] = "s-maxage=60"
    return response


@account_bp.post("/account/keys")
def revoke_service_keys():
    account: Account = g.get("account")
    if not account:
        return redirect(url_for("account.get_login"))

    keys_dict = request.form.to_dict(flat=False)
    keys_for_revocation = [int(key) for key in keys_dict["revoke"]] if keys_dict.get("revoke") else []

    revoke_saved_keys(keys_for_revocation, account.id)

    props = SuccessProps(currentPage="account", redirect="/account/keys")

    response = make_response(render_template("success.html", props=props), 200)
    return response


@account_bp.get("/account/login")
def get_login():
    props = {"currentPage": "login"}
    location = request.form.get("location", request.args.get("location", url_for("artists.list")))

    if account := g.get("account"):
        return redirect(set_query_parameter(location, {"logged_in": "yes", "role": account.role}))

    response = make_response(
        render_template(
            "account/login.html",
            location=location,
            props=props,
        ),
        200,
    )
    response.headers["Cache-Control"] = "s-maxage=60"
    return response


@account_bp.post("/account/login")
def post_login():
    location = request.form.get("location", request.args.get("location", url_for("artists.list")))

    if account := g.get("account"):
        return redirect(set_query_parameter(location, {"logged_in": "yes", "role": account.role}))

    username = request.form.get("username", "").replace("\x00", "")
    password = request.form.get("password", "")

    if account := attempt_login(username, password):
        return redirect(set_query_parameter(location, {"logged_in": "yes", "role": account.role}))

    return redirect(url_for("account.get_login", location=location))



@account_bp.route("/account/logout")
def logout():
    if "account_id" in session:
        session.pop("account_id")
    return redirect(url_for("artists.list"))


@account_bp.get("/account/register")
def get_register():
    props = {
        "currentPage": "login",
        "username_regex": USERNAME_REGEX.pattern,
    }
    location = request.form.get("location", request.args.get("location", url_for("artists.list")))

    if g.get("account"):
        return redirect(location)

    return make_response(render_template("account/register.html", props=props, location=location), 200)


@account_bp.post("/account/register")
def post_register():
    location = request.form.get("location", request.args.get("location", url_for("artists.list")))

    username = request.form.get("username", "").replace("\x00", "").strip()
    password = request.form.get("password", "").strip()
    favorites_json = request.form.get("favorites", "[]")
    confirm_password = request.form.get("confirm_password", "").strip()

    favorites = []
    if favorites_json != "":
        try:
            favorites = orjson.loads(favorites_json)
        except JSONDecodeError:
            pass

    errors = False
    if username == "":
        flash("Username cannot be empty")
        errors = True

    if not USERNAME_REGEX.match(username):
        flash("Invalid username")
        errors = True

    if password == "":
        flash("Password cannot be empty")
        errors = True

    if password != confirm_password:
        flash("Passwords do not match")
        errors = True

    if current_app.config.get("ENABLE_PASSWORD_VALIDATOR") and is_password_compromised(password):
        flash(
            "We've detected that password was compromised in a data breach on another site. Please choose a different password."
        )
        errors = True

    if not errors:
        success = create_account(username, password, favorites)
        if not success:
            flash("Username already taken")
            errors = True

    if not errors:
        account = attempt_login(username, password)
        if account is None:
            current_app.logger.warning("Error logging into account immediately after creation")
            flash("Account created successfully.")
            return redirect(set_query_parameter(location, {"logged_in": "yes"}))
        else:
            flash("Account created successfully.")
            return redirect(set_query_parameter(location, {"logged_in": "yes", "role": account.role}))

    return redirect(url_for("account.get_register", location=location))


@account_bp.get("/account/change_password")
@require_login
def change_password(user: Account):
    props = {"currentPage": "changepassword"}

    tmpl = render_template("account/change_password.html", props=props)
    response = make_response(tmpl, 200)
    response.headers["Cache-Control"] = "s-maxage=3600"
    return response


@account_bp.post("/account/change_password")
@require_login
def post_change_password(user: Account):
    current_password = request.form.get("current-password", "").strip()
    new_password = request.form.get("new-password", "").strip()
    new_password_conf = request.form.get("new-password-confirmation", "").strip()

    errors = False

    if not new_password:
        flash("Password cannot be empty")
        errors = True

    if new_password != new_password_conf:
        flash("Passwords do not match")
        errors = True

    if current_app.config.get("ENABLE_PASSWORD_VALIDATOR") and is_password_compromised(new_password):
        flash(
            "We've detected that password was compromised in a data breach on another site. Please choose a different password."
        )
        errors = True

    if not errors:
        if db_change_password(user.id, current_password, new_password):
            flash("Password changed")
            return redirect(url_for("account.get_account"))
        else:
            flash("Current password is invalid")

    return redirect(url_for("account.change_password"))


@account_bp.get("/.well-known/change-password")
def well_known_change_password():
    response = redirect(url_for("account.change_password"))
    response.headers["Cache-Control"] = "s-maxage=604800"
    return response


account_bp.register_blueprint(administrator, url_prefix="/account")
account_bp.register_blueprint(moderator, url_prefix="/account")
