from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response, jsonify, abort, after_this_request
from config import Config
from app.models.user import User
from app.extensions import redis_controller, get_remote_address, csrf, limiter
from app.util import auth, websiteFeatures
import string
from datetime import datetime, timedelta
import secrets
import logging
from sqlalchemy import func
from app.pages.login.login import CreateLoginRecord

config = Config()
AuthenticationRoute = Blueprint('authentication', __name__, url_prefix='/')

@AuthenticationRoute.route('/Login/Negotiate.ashx', methods=['GET'])
def loginNegotiate():
    AuthenticationTicket = request.args.get(
        key = 'suggest',
        default = None,
        type = str
    )
    if AuthenticationTicket is None:
        return 'Invalid request',400
    
    authticketInfo = redis_controller.get(f"authticket:{AuthenticationTicket}")
    if authticketInfo is None:
        return 'Invalid request',400
    redis_controller.delete(f"authticket:{AuthenticationTicket}")

    userId = int(authticketInfo)
    newAuthToken = auth.CreateToken(userId, get_remote_address())
    resp = make_response(newAuthToken, 200)
    resp.headers["Content-Type"] = "text/plain"
    resp.set_cookie(".ROBLOSECURITY", newAuthToken, expires=datetime.utcnow() + timedelta(days=365), domain=f".{config.BaseDomain}")

    return resp

@AuthenticationRoute.route('/v2/twostepverification/verify', methods=['POST'])
@limiter.limit("12/minute")
@csrf.exempt
def verifyTwoStep():
    if not request.is_json:
        return jsonify( { "errors": [ { "code": 1, "message": "Invalid request." } ] } ), 400
    
    try:
        assert "username" in request.json, "Missing parameter: username"
        assert "code" in request.json, "Missing parameter: code"
        assert "ticket" in request.json, "Missing parameter: ticket"
        assert isinstance(request.json["username"], str), "Invalid parameter type: username, expected string"
        assert isinstance(request.json["code"], int), "Invalid parameter type: code, expected integer"
        assert isinstance(request.json["ticket"], str), "Invalid parameter type: ticket, expected string"
    except Exception as e:
        return jsonify( { "errors": [ { "code": 1, "message": f"Validation failed, {str(e)}" } ] } ), 400

    GivenUsername : str = request.json["username"]
    GivenTwoStepCode : int = request.json["code"]
    GivenTicket : str = request.json["ticket"]

    if redis_controller.exists(f"twofactorticket:{GivenTicket}") == 0:
        return jsonify( { "errors": [ { "code": 5, "message": "Invalid two step verification ticket." } ] } ), 400
    TwoFactorSessionTicketInfo = redis_controller.get(f"twofactorticket:{GivenTicket}")
    if TwoFactorSessionTicketInfo is None:
        return jsonify( { "errors": [ { "code": 5, "message": "Invalid two step verification ticket." } ] } ), 400
    redis_controller.delete(f"twofactorticket:{GivenTicket}")

    UserId = int(TwoFactorSessionTicketInfo)

    Validated2FA = auth.Validate2FACode(UserId, GivenTwoStepCode)
    if not Validated2FA:
        return jsonify( { "errors": [ { "code": 6, "message": "The code is invalid." } ] } ), 400
    
    if Validated2FA:
        Response = make_response(jsonify({}))
        sessionToken = auth.CreateToken(userid=UserId, ip=get_remote_address())
        Response.set_cookie(".ROBLOSECURITY", sessionToken, expires=datetime.utcnow() + timedelta(days=365), domain=f".{config.BaseDomain}")
        return Response


@AuthenticationRoute.route('/Login/NewAuthTicket', methods=['POST'])
@csrf.exempt
@auth.authenticated_required_api
def loginNewAuthTicket():
    userId = auth.GetCurrentUser().id

    authticket = ''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(256))
    redis_controller.set(f"authticket:{authticket}", userId, 60*10)
    return authticket,200

@AuthenticationRoute.route('/game/GetCurrentUser.ashx', methods=['GET'])
def gameGetCurrentUser():
    AuthenticatedUser = auth.GetCurrentUser()
    if AuthenticatedUser is None:
        return "Bad Request", 200
    return str(AuthenticatedUser.id), 200

@AuthenticationRoute.route('/login/RequestAuth.ashx', methods=['GET'])
@limiter.limit("6/minute")
def loginRequestAuth():
    AuthenticatedUser : User = auth.GetCurrentUser()
    if AuthenticatedUser is None:
        return "User is not authorized.", 401
    NewAuthTicket = ''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(256))
    redis_controller.set(f"authticket:{NewAuthTicket}", AuthenticatedUser.id, 60*10)

    resp = make_response(
        f"{config.BaseURL}/Login/Negotiate.ashx?suggest={NewAuthTicket}",
        200
    )
    resp.headers["Content-Type"] = "text/plain"
    return resp

@AuthenticationRoute.route('/game/logout.aspx')
@auth.authenticated_required
def gameLogout():
    auth.invalidateToken(request.cookies.get(".ROBLOSECURITY"))
    resp = make_response(redirect("/login"))
    resp.set_cookie(".ROBLOSECURITY", "", expires=0)
    return resp

@AuthenticationRoute.route('/v2/login', methods=['POST'])
@limiter.limit("12/minute")
@csrf.exempt
def LoginRoute():
    loginEnabled = websiteFeatures.GetWebsiteFeature("WebLogin")
    if not loginEnabled:
        return jsonify( { "errors": [ { "code": 11, "message": "Service unavailable. Please try again." } ] } ), 503

    if not request.is_json:
        return abort(400)
    
    AuthenticatedUser : User = auth.GetCurrentUser()
    if AuthenticatedUser and request.user_agent.string != "RobloxStudio/WinInet RobloxApp/0.450.0.411923 (GlobalDist; RobloxDirectDownload)":
        return redirect("/home")
    
    if AuthenticatedUser and request.user_agent.string == "RobloxStudio/WinInet RobloxApp/0.450.0.411923 (GlobalDist; RobloxDirectDownload)":
        Response = make_response(jsonify({
            "username": AuthenticatedUser.username,
            "isUnder13": False,
            "userId": AuthenticatedUser.id,
            "countryCode": "US",
            "membershipType": 4,
            "displayName": AuthenticatedUser.username
        }))
        return Response
    
    if ( "username" not in request.json and "cvalue" not in request.json ) or "password" not in request.json:
        return jsonify( { "errors": [ { "code": 1, "message": "Incorrect username or password. Please try again." } ] } ), 403
    if "username" in request.json:
        Username = str(request.json["username"])
    else:
        Username = str(request.json["cvalue"])
    Password = str(request.json["password"])

    UserObject : User = User.query.filter(func.lower(User.username) == func.lower(Username)).first()
    if not UserObject:
        return jsonify( { "errors": [ { "code": 1, "message": "Incorrect username or password. Please try again." } ] } ), 403
    
    if UserObject.accountstatus != 1:
        return jsonify( { "errors": [ { "code": 1, "message": "Incorrect username or password. Please try again." } ] } ), 403
    
    ActualPassword = Password
    if UserObject.TOTPEnabled:
        if request.user_agent.string == "RobloxStudio/WinInet RobloxApp/0.450.0.411923 (GlobalDist; RobloxDirectDownload)":
            if not auth.VerifyPassword(UserObject, ActualPassword):
                return jsonify( { "errors": [ { "code": 1, "message": "Incorrect username or password. Please try again." } ] } ), 403 
            twofactorticket = ''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(60))
            redis_controller.set(f"twofactorticket:{twofactorticket}", UserObject.id, 60*10) 
            return jsonify({"user": {
                "id": UserObject.id,
                "name": UserObject.username,
                "displayName": UserObject.username
            }, "twoStepVerificationData": {
                "mediaType": "Email",
                "ticket": twofactorticket
            }, "identityVerificationLoginTicket": twofactorticket })
        if ";" not in Password:
            return jsonify( { "errors": [ { "code": 1, "message": "Incorrect username or password. Please try again." } ] } ), 403
        ActualPassword = Password.split(";")[0]
        TOTPCode = Password.split(";")[1]

        if not auth.Validate2FACode(UserObject.id, TOTPCode):
            return jsonify( { "errors": [ { "code": 1, "message": "Incorrect username or password. Please try again." } ] } ), 403
    
    if not auth.VerifyPassword(UserObject, ActualPassword):
        return jsonify( { "errors": [ { "code": 1, "message": "Incorrect username or password. Please try again." } ] } ), 403
    
    sessionToken = auth.CreateToken(userid=UserObject.id, ip=get_remote_address())
    Response = make_response(jsonify({
        "username": UserObject.username,
        "isUnder13": False,
        "userId": UserObject.id,
        "countryCode": "US",
        "membershipType": 4,
        "displayName": UserObject.username
    }))
    Response.set_cookie(".ROBLOSECURITY", sessionToken, expires=datetime.utcnow() + timedelta(days=365), domain=f".{config.BaseDomain}")
    CreateLoginRecord( UserObject.id )
    #logging.info(f"/v2/login - {UserObject.username} ({UserObject.id}) [{request.user_agent}] logged in successfully")

    return Response

@AuthenticationRoute.route("/v1/logout", methods=["POST"])
@AuthenticationRoute.route("/v2/logout", methods=["POST"])
@auth.authenticated_required_api
@csrf.exempt
def LogoutRoute():
    auth.invalidateToken(request.cookies.get(".ROBLOSECURITY"))
    resp = make_response(jsonify({}))
    resp.set_cookie(".ROBLOSECURITY", "", expires=0)
    return resp

@AuthenticationRoute.route("/v2/passwords/current-status", methods=["GET"])
@auth.authenticated_required_api
def PasswordsCurrentStatusRoute():
    return jsonify({"valid": True})
