# Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
#
# This software is licensed under the Apache License, Version 2.0 (the
# "License") as published by the Apache Software Foundation.
#
# You may not use this file except in compliance with the License. You may
# obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from typing import Any, Dict
from supertokens_python.recipe.session.syncio import update_access_token_payload
from supertokens_python.recipe.session.interfaces import APIInterface, RecipeInterface
from supertokens_python.framework import BaseRequest, BaseResponse
import json
import os
import sys
from functools import wraps
from typing import Union

from typing_extensions import Literal

from flask import Flask, g, jsonify, make_response, render_template, request
from flask.wrappers import Response
from flask_cors import CORS
from supertokens_python import InputAppInfo, Supertokens, SupertokensConfig, init
from supertokens_python.framework.flask.flask_middleware import Middleware
from supertokens_python.recipe import session
from supertokens_python.recipe.session import (
    InputErrorHandlers,
    SessionContainer,
    SessionRecipe,
)
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.session.syncio import (
    create_new_session,
    revoke_all_sessions_for_user,
)

last_set_enable_anti_csrf = True
last_set_enable_jwt = False

index_file = open("templates/index.html", "r")
file_contents = index_file.read()
index_file.close()

app = Flask(__name__, template_folder="templates")
Middleware(app)
CORS(app, supports_credentials=True)
os.environ.setdefault("SUPERTOKENS_ENV", "testing")


def custom_decorator_for_test():  # type: ignore
    def session_verify_custom_test(f):  # type: ignore
        @wraps(f)  # type: ignore
        def wrapped_function(*args, **kwargs):  # type: ignore
            Test.increment_attempted_refresh()
            try:
                value: Response = f(*args, **kwargs)
                if value is not None and value.status_code != 200:
                    return value
                if request.headers.get("rid") is None:  # type: ignore
                    return "refresh failed"
                Test.increment_refresh()
                return "refresh success"
            except Exception as e:
                raise e

        return wrapped_function  # type: ignore

    return session_verify_custom_test  # type: ignore


def try_refresh_token(_):
    return jsonify({"error": "try refresh token"}), 401


def unauthorised(_):
    return jsonify({"error": "unauthorised"}), 401


class Test:
    no_of_times_refresh_called_during_test = 0
    no_of_times_get_session_called_during_test = 0
    no_of_times_refresh_attempted_during_test = 0

    @staticmethod
    def reset():
        Test.no_of_times_refresh_called_during_test = 0
        Test.no_of_times_get_session_called_during_test = 0
        Test.no_of_times_refresh_attempted_during_test = 0

    @staticmethod
    def increment_refresh():
        Test.no_of_times_refresh_called_during_test = (
            Test.no_of_times_refresh_called_during_test + 1
        )

    @staticmethod
    def increment_attempted_refresh():
        Test.no_of_times_refresh_attempted_during_test = (
            Test.no_of_times_refresh_attempted_during_test + 1
        )

    @staticmethod
    def increment_get_session():
        Test.no_of_times_get_session_called_during_test = (
            Test.no_of_times_get_session_called_during_test + 1
        )

    @staticmethod
    def get_session_called_count():
        return Test.no_of_times_get_session_called_during_test

    @staticmethod
    def get_refresh_called_count():
        return Test.no_of_times_refresh_called_during_test

    @staticmethod
    def get_refresh_attempted_count():
        return Test.no_of_times_refresh_attempted_during_test


async def unauthorised_f(_: BaseRequest, __: str, res: BaseResponse):
    res.set_status_code(401)
    res.set_json_content({})
    return res


def apis_override_session(param: APIInterface):
    param.disable_refresh_post = True
    return param


def functions_override_session(param: RecipeInterface):
    original_create_new_session = param.create_new_session

    async def create_new_session_custom(
        _request: BaseRequest,
        user_id: str,
        access_token_payload: Union[Dict[str, Any], None],
        session_data: Union[Dict[str, Any], None],
        user_context: Dict[str, Any],
    ) -> SessionContainer:
        if access_token_payload is None:
            access_token_payload = {}
        access_token_payload = {**access_token_payload, "customClaim": "customValue"}
        return await original_create_new_session(
            _request, user_id, access_token_payload, session_data, user_context
        )

    param.create_new_session = create_new_session_custom

    return param


def get_app_port():
    argvv = sys.argv
    for i in range(0, len(argvv)):  # pylint: disable=consider-using-enumerate
        if argvv[i] == "--port":
            return argvv[i + 1]

    return "8080"


def config(
    enable_anti_csrf: bool, enable_jwt: bool, jwt_property_name: Union[str, None]
):
    anti_csrf: Literal["VIA_TOKEN", "NONE"] = "NONE"
    if enable_anti_csrf:
        anti_csrf = "VIA_TOKEN"
    if enable_jwt:
        init(
            supertokens_config=SupertokensConfig("http://localhost:9000"),
            app_info=InputAppInfo(
                app_name="SuperTokens Python SDK",
                api_domain="0.0.0.0:" + get_app_port(),
                website_domain="http://localhost.org:8080",
            ),
            framework="flask",
            recipe_list=[
                session.init(
                    error_handlers=InputErrorHandlers(on_unauthorised=unauthorised_f),
                    anti_csrf=anti_csrf,
                    override=session.InputOverrideConfig(
                        apis=apis_override_session, functions=functions_override_session
                    ),
                    jwt=session.JWTConfig(enable_jwt, jwt_property_name),
                )
            ],
            telemetry=False,
        )
    else:
        init(
            supertokens_config=SupertokensConfig("http://localhost:9000"),
            app_info=InputAppInfo(
                app_name="SuperTokens Python SDK",
                api_domain="0.0.0.0:" + get_app_port(),
                website_domain="http://localhost.org:8080",
            ),
            framework="flask",
            recipe_list=[
                session.init(
                    error_handlers=InputErrorHandlers(on_unauthorised=unauthorised_f),
                    anti_csrf=anti_csrf,
                    override=session.InputOverrideConfig(apis=apis_override_session),
                )
            ],
            telemetry=False,
        )


config(True, False, None)


@app.route("/index.html", methods=["GET"])  # type: ignore
def send_file():
    return render_template("index.html")


from flask import send_from_directory  # type: ignore


@app.route("/angular/<path:path>", methods=["GET"])  # type: ignore
def send_angular_file(path: str):
    return send_from_directory("templates/angular", path)


def send_options_api_response():
    return ""


@app.route("/login", methods=["OPTIONS"])  # type: ignore
def login_options():
    return send_options_api_response()


@app.route("/login", methods=["POST"])  # type: ignore
def login():
    user_id: str = request.get_json()["userId"]  # type: ignore
    _session = create_new_session(request, user_id)
    return _session.get_user_id()


@app.route("/beforeeach", methods=["OPTIONS"])  # type: ignore
def before_each_options():
    return send_options_api_response()


@app.route("/beforeeach", methods=["POST"])  # type: ignore
def before_each():
    Test.reset()
    return ""


@app.route("/testUserConfig", methods=["OPTIONS"])  # type: ignore
def test_user_config_options():
    return send_options_api_response()


@app.route("/testUserConfig", methods=["POST"])  # type: ignore
def test_config():
    return ""


@app.route("/multipleInterceptors", methods=["OPTIONS"])  # type: ignore
def multiple_interceptors_options():
    return send_options_api_response()


@app.route("/multipleInterceptors", methods=["POST"])  # type: ignore
def multiple_interceptors():
    result_bool = "success" if "interceptorheader2" in request.headers and "interceptorheader1" in request.headers else "failure"  # type: ignore
    return str(result_bool)


@app.route("/", methods=["OPTIONS"])  # type: ignore
def options():
    return send_options_api_response()


@app.route("/", methods=["GET"])  # type: ignore
@verify_session()
def get_info():
    Test.increment_get_session()
    session = g.supertokens
    resp = make_response(session.get_user_id())
    resp.headers["Cache-Control"] = "no-cache, private"
    return resp


@app.route("/update-jwt", methods=["OPTIONS"])  # type: ignore
def update_options():
    return send_options_api_response()


@app.route("/update-jwt", methods=["GET"])  # type: ignore
@verify_session()
# @supertokens_middleware(True)
def update_jwt():
    Test.increment_get_session()
    _session = g.supertokens

    resp = make_response(_session.get_access_token_payload())
    resp.headers["Cache-Control"] = "no-cache, private"
    return resp


@app.route("/update-jwt", methods=["POST"])  # type: ignore
@verify_session()
# @supertokens_middleware()
def update_jwt_post():
    _session = g.supertokens
    _session.sync_update_access_token_payload(request.get_json())
    Test.increment_get_session()
    resp = make_response(_session.get_access_token_payload())
    resp.headers["Cache-Control"] = "no-cache, private"
    return resp


@app.route("/update-jwt-with-handle", methods=["POST"])  # type: ignore
@verify_session()
def update_jwt_with_handle_post():
    _session = g.supertokens
    update_access_token_payload(_session.get_handle(), request.get_json())
    resp = make_response(_session.get_access_token_payload())
    resp.headers["Cache-Control"] = "no-cache, private"
    return resp


@app.route("/testing", methods=["OPTIONS"])  # type: ignore
def testing_options():
    return send_options_api_response()


@app.route("/testing", methods=["GET"])  # type: ignore
def testing():
    if "testing" in request.headers:  # type: ignore
        resp = make_response("success")
        resp.headers["testing"] = request.headers["testing"]  # type: ignore
        return resp
    return "success"


@app.route("/testing", methods=["PUT"])  # type: ignore
def testing_put():
    if "testing" in request.headers:  # type: ignore
        resp = make_response("success")
        resp.headers["testing"] = request.headers["testing"]  # type: ignore
        return resp
    return "success"


@app.route("/testing", methods=["POST"])  # type: ignore
def testing_post():
    if "testing" in request.headers:  # type: ignore
        resp = make_response("success")
        resp.headers["testing"] = request.headers["testing"]  # type: ignore
        return resp
    return "success"


@app.route("/testing", methods=["DELETE"])  # type: ignore
def testing_delete():
    if "testing" in request.headers:  # type: ignore
        resp = make_response("success")
        resp.headers["testing"] = request.headers["testing"]  # type: ignore
        return resp
    return "success"


@app.route("/logout", methods=["OPTIONS"])  # type: ignore
def logout_options():
    return send_options_api_response()


@app.route("/logout", methods=["POST"])  # type: ignore
@verify_session()
def logout():
    _session = g.supertokens

    _session.sync_revoke_session()
    return "success"


@app.route("/revokeAll", methods=["OPTIONS"])  # type: ignore
def revoke_all_options():
    return send_options_api_response()


@app.route("/revokeAll", methods=["POST"])  # type: ignore
@verify_session()
async def revoke_all():
    session_ = g.supertokens
    revoke_all_sessions_for_user(session_.get_user_id())
    return "success"


@app.route("/refresh", methods=["OPTIONS"])  # type: ignore
def refresh_options():
    return send_options_api_response()


@app.route("/refreshAttemptedTime", methods=["GET"])  # type: ignore
def refresh_attempted_time():
    return str(Test.get_refresh_attempted_count())


@app.route("/auth/session/refresh", methods=["POST"])  # type: ignore
@custom_decorator_for_test()
@verify_session()
def refresh():
    return ""


@app.route("/setAntiCsrf", methods=["POST"])  # type: ignore
def set_anti_csrf():
    global last_set_enable_anti_csrf
    json: Dict[str, Any] = request.get_json(silent=True)  # type: ignore
    if "enableAntiCsrf" not in json:
        enable_csrf = True
    else:
        enable_csrf = json["enableAntiCsrf"]

    last_set_enable_anti_csrf = enable_csrf
    if enable_csrf is not None:
        Supertokens.reset()
        SessionRecipe.reset()
        config(enable_csrf, False, None)
    return "success", 200


@app.route("/setEnableJWT", methods=["POST"])  # type: ignore
def set_enable_jwt():
    global last_set_enable_jwt
    global last_set_enable_anti_csrf  # pylint: disable=global-variable-not-assigned
    json: Dict[str, Any] = request.get_json(silent=True)  # type: ignore
    if "enableJWT" not in json:
        enable_jwt = False
    else:
        enable_jwt = json["enableJWT"]

    last_set_enable_jwt = enable_jwt
    if enable_jwt is not None:
        Supertokens.reset()
        SessionRecipe.reset()
        config(last_set_enable_anti_csrf, enable_jwt, None)
    return "success", 200


@app.route("/refreshCalledTime", methods=["OPTIONS"])  # type: ignore
def refresh_called_time_options():
    return send_options_api_response()


@app.route("/refreshCalledTime", methods=["GET"])  # type: ignore
def refresh_called_time():
    return str(Test.get_refresh_called_count())


@app.route("/getSessionCalledTime", methods=["OPTIONS"])  # type: ignore
def get_session_called_time_options():
    return send_options_api_response()


@app.route("/getSessionCalledTime", methods=["GET"])  # type: ignore
def get_session_called_time():
    return str(Test.get_session_called_count())


@app.route("/ping", methods=["OPTIONS"])  # type: ignore
def ping_options():
    return send_options_api_response()


@app.route("/ping", methods=["GET"])  # type: ignore
def ping():
    return "success"


@app.route("/testHeader", methods=["OPTIONS"])  # type: ignore
def test_header_options():
    return send_options_api_response()


@app.route("/testHeader", methods=["GET"])  # type: ignore
def test_header():  # type: ignore
    success_info = request.headers.get("st-custom-header")  # type: ignore
    return {"success": success_info}  # type: ignore


@app.route("/checkDeviceInfo", methods=["OPTIONS"])  # type: ignore
def check_device_info_options():
    return send_options_api_response()


@app.route("/checkDeviceInfo", methods=["GET"])  # type: ignore
def check_device_info():
    sdk_name = request.headers.get("supertokens-sdk-name")  # type: ignore
    sdk_version = request.headers.get("supertokens-sdk-version")  # type: ignore
    return "true" if sdk_name == "website" and isinstance(sdk_version, str) else "false"


@app.route("/check-rid", methods=["GET"])  # type: ignore
def check_rid():
    rid = request.headers.get("rid")  # type: ignore

    return "fail" if rid is None else "success"


@app.route("/featureFlags", methods=["GET"])  # type: ignore
def feature_flags():
    global last_set_enable_jwt  # pylint: disable=global-variable-not-assigned

    return jsonify({"sessionJwt": last_set_enable_jwt})


@app.route("/reinitialiseBackendConfig", methods=["POST"])  # type: ignore
def reinitialize():
    global last_set_enable_jwt  # pylint: disable=global-variable-not-assigned
    global last_set_enable_anti_csrf  # pylint: disable=global-variable-not-assigned
    jwt_property_name: Union[str, None] = None
    json: Dict[str, Any] = request.get_json(silent=True)  # type: ignore
    if "jwtPropertyName" in json:
        jwt_property_name = json["jwtPropertyName"]

    Supertokens.reset()
    SessionRecipe.reset()
    config(last_set_enable_anti_csrf, last_set_enable_jwt, jwt_property_name)
    return "", 200


@app.route("/checkAllowCredentials", methods=["OPTIONS"])  # type: ignore
def check_allow_credentials_options():
    return send_options_api_response()


@app.route("/checkAllowCredentials", methods=["GET"])  # type: ignore
def check_allow_credentials():
    return jsonify(json.dumps("allow-credentials" in request.headers))  # type: ignore


@app.route("/testError", methods=["GET", "OPTIONS"])  # type: ignore
def test_error():
    if request.method == "OPTIONS":  # type: ignore
        return send_options_api_response()

    status_code = int(request.args.get("code", "500"))
    return Response("test error message", status=status_code)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=int(get_app_port()), threaded=True)
