from typing import TypedDict, List, Literal, Optional

from flask import Blueprint, g, make_response, jsonify, abort, request, current_app

from src.config import Configuration
from src.lib.account import (
    is_logged_in,
    load_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.lib.api import create_client_error_response
from src.lib.dms import approve_dms, cleanup_unapproved_dms, get_unapproved_dms, clean_dms_already_approved
from src.pages.account.types import AccountPageProps, NotificationsProps, ServiceKeysProps
from src.types.props import SuccessProps
from src.types.account.account import Account
from src.types.kemono import Unapproved_DM


from . import v1api_bp
from .moderator import moderator_bp

account_bp = Blueprint("account", __name__)


# check credentials for all requests for this blueprint
# so the subsequent handlers wouldn't need to check it again
@account_bp.before_request
def check_auth():
    if not is_logged_in():
        abort(401)

    account = load_account()

    if not account:
        abort(401)


@account_bp.get("/account")
def get_account_info():
    account: Account = g.get("account")

    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(jsonify(props=props), 200)


TDChangePasswordBody = TypedDict(
    "TDChangePasswordBody", {"current-password": str, "new-password": str, "new-password-confirmation": str}
)


@account_bp.post("/account/change_password")
def post_change_password():
    body: TDChangePasswordBody = request.get_json()
    current_password = body.get("current-password", "")
    new_password = body.get("new-password", "").strip()
    new_password_conf = body.get("new-password-confirmation", "").strip()
    account: Account = load_account()

    if not new_password:
        return create_client_error_response("New password cannot be empty.")

    if len(new_password) < 5:
        return create_client_error_response("New password must have at least 5 characters.")

    if new_password != new_password_conf:
        return create_client_error_response("New password and confirmation do not match.")

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

        return response

    if not db_change_password(account.id, current_password, new_password):
        return create_client_error_response("Current password is invalid.")

    response = make_response(jsonify(True), 200)

    return response


@account_bp.get("/account/notifications")
def get_notifications():
    account: Account = g.get("account")

    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(jsonify(props=props), 200)


@account_bp.get("/account/keys")
def get_account_keys():
    account: Account = g.get("account")

    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(
        jsonify(props=props, import_ids=saved_session_key_import_ids),
        200,
    )
    response.headers["Cache-Control"] = "s-maxage=60"

    return response


class TDKeyRevokeBody(TypedDict):
    revoke: list[int]


@account_bp.post("/account/keys")
def revoke_service_keys():
    account: Account = g.get("account")
    body: TDKeyRevokeBody = request.get_json()
    keys_for_revocation = body["revoke"]

    revoke_saved_keys(keys_for_revocation, account.id)

    props = SuccessProps(currentPage="account", redirect="/account/keys")
    response = make_response(jsonify(props=props), 200)

    return response


@account_bp.get("/account/posts/upload")
def upload_post():
    account: Account = g.get("account")

    required_roles = Configuration().filehaus["required_roles"]
    if len(required_roles) and account.role not in required_roles:
        return create_client_error_response(
            "Filehaus uploading requires elevated permissions. Please contact the administrator to change your role."
        )

    props = {"currentPage": "posts"}
    response = make_response(jsonify(props), 200)
    response.headers["Cache-Control"] = "s-maxage=60"

    return response


class TDDMPageProps(TypedDict):
    account_id: int
    dms: List[Unapproved_DM]
    status: str
    currentPage: Literal["import"]


@account_bp.get("/account/review_dms")
def importer_dms():
    account: Account = g.get("account")

    status = "ignored" if request.args.get("status") == "ignored" else "pending"
    dms = get_unapproved_dms(account.id, request.args.get("status") == "ignored")

    props = TDDMPageProps(currentPage="import", account_id=account.id, dms=dms, status=status)

    response = make_response(
        jsonify(
            props,
        ),
        200,
    )
    response.headers["Cache-Control"] = "max-age=0, private, must-revalidate"

    return response


class TDApproveDMsBody(TypedDict):
    approved_hashes: list[str]
    delete_ignored: Optional[bool]


@account_bp.post("/account/review_dms")
def approve_importer_dms():
    account: Account = g.get("account")
    body: TDApproveDMsBody = request.get_json()
    approved_hashes = body.get("approved_hashes", [])
    delete_ignored = bool(body.get("delete_ignored", False))
    approve_dms(int(account.id), approved_hashes)
    clean_dms_already_approved(int(account.id))
    cleanup_unapproved_dms(int(account.id))

    if delete_ignored:
        cleanup_unapproved_dms(int(account.id), delete=True)

    response = make_response(jsonify(True), 200)
    response.headers["Cache-Control"] = "max-age=0, private, must-revalidate"

    return response


@account_bp.errorhandler(401)
def not_authorized_error(error):
    return (jsonify(error="Not Authorized"), 401)

account_bp.register_blueprint(moderator_bp, url_prefix="/account")
# not sure about resolution order
# so load it in the end
v1api_bp.register_blueprint(account_bp)
