# games.roblox.com

import math
from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response, jsonify, abort, after_this_request
from app.extensions import redis_controller, get_remote_address, csrf, limiter, db
from app.util import auth, websiteFeatures, placeinfo
from app.models.place import Place
from app.models.asset import Asset
from app.models.placeservers import PlaceServer
from app.models.user import User
from app.models.groups import Group
from app.models.asset_thumbnail import AssetThumbnail
from app.models.universe import Universe
from app.enums.PlaceYear import PlaceYear
from app.routes.rate import GetAssetLikesAndDislikes, GetAssetFavoriteCount, GetUserFavoriteStatus
from flask_wtf.csrf import CSRFError, generate_csrf
from sqlalchemy import func, and_
from flask_sqlalchemy import pagination

GamesAPIRoute = Blueprint('gamesapi', __name__, url_prefix='/')

csrf.exempt(GamesAPIRoute)
@GamesAPIRoute.errorhandler(CSRFError)
def handle_csrf_error(e):
    ErrorResponse = make_response(jsonify({
        "errors": [
            {
                "code": 0,
                "message": "Token Validation Failed"
            }
        ]
    }))

    ErrorResponse.status_code = 403
    ErrorResponse.headers["x-csrf-token"] = generate_csrf()
    return ErrorResponse

@GamesAPIRoute.errorhandler(429)
def handle_ratelimit_reached(e):
    return jsonify({
        "errors": [
            {
                "code": 9,
                "message": "The flood limit has been exceeded."
            }
        ]
    }), 429

@GamesAPIRoute.before_request
def before_request():
    if "Roblox/" not in request.user_agent.string:
        csrf.protect()


@GamesAPIRoute.route('/v1/games/sorts', methods=["GET"])
@auth.authenticated_required_api
def games_sorts():
    return jsonify({
        "sorts" : [
            {
                "token": "MostPopular",
                "name": "most_popular",
                "displayName": "Popular",
                "gameSetTypeId": 1,
                "gameSetTargetId": 90,
                "timeOptionsAvailable": False,
                "genreOptionsAvailable": False,
                "numberOfRows": 2,
                "numberOfGames": 0,
                "isDefaultSort": True,
                "contextUniverseId": None,
                "contextCountryRegionId": 1,
                "tokenExpiryInSeconds": 3600
            },
            {
                "token": "Featured",
                "name": "featured",
                "displayName": "Featured",
                "gameSetTypeId": 2,
                "gameSetTargetId": 91,
                "timeOptionsAvailable": False,
                "genreOptionsAvailable": False,
                "numberOfRows": 1,
                "numberOfGames": 0,
                "isDefaultSort": True,
                "contextUniverseId": None,
                "contextCountryRegionId": 1,
                "tokenExpiryInSeconds": 3600
            },
            {
                "token": "RecentlyUpdated",
                "name": "recently_updated",
                "displayName": "Recently Updated",
                "gameSetTypeId": 3,
                "gameSetTargetId": 93,
                "timeOptionsAvailable": False,
                "genreOptionsAvailable": False,
                "numberOfRows": 1,
                "numberOfGames": 0,
                "isDefaultSort": True,
                "contextUniverseId": None,
                "contextCountryRegionId": 1,
                "tokenExpiryInSeconds": 3600
            },
        ],
        "timeFilters": [
            {
                "token": "Now",
                "name": "Now",
                "tokenExpiryInSeconds": 3600
            },
            {
                "token": "PastDay",
                "name": "PastDay",
                "tokenExpiryInSeconds": 3600
            },
            {
                "token": "PastWeek",
                "name": "PastWeek",
                "tokenExpiryInSeconds": 3600
            },
            {
                "token": "PastMonth",
                "name": "PastMonth",
                "tokenExpiryInSeconds": 3600
            },
            {
                "token": "AllTime",
                "name": "AllTime",
                "tokenExpiryInSeconds": 3600
            }
        ],
        "genreFilters": [
            {
            "token": "T638364961735517991_1_89de",
            "name": "All",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518009_19_3d2",
            "name": "Building",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518045_11_3de6",
            "name": "Horror",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518062_7_558c",
            "name": "Town and City",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518076_17_c371",
            "name": "Military",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518094_15_2056",
            "name": "Comedy",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518107_8_6d4f",
            "name": "Medieval",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518120_13_c168",
            "name": "Adventure",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518134_9_e6aa",
            "name": "Sci-Fi",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518156_12_13fb",
            "name": "Naval",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518169_20_46a",
            "name": "FPS",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518183_21_4bbf",
            "name": "RPG",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518192_14_efc6",
            "name": "Sports",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518205_10_fa83",
            "name": "Fighting",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518223_16_5d38",
            "name": "Western",
            "tokenExpiryInSeconds": 3600
            }
        ],
        "gameFilters": [
            {
            "token": "T638364961735518263_Any_56d2",
            "name": "Any",
            "tokenExpiryInSeconds": 3600
            },
            {
            "token": "T638364961735518277_Classic_a1f4",
            "name": "Classic",
            "tokenExpiryInSeconds": 3600
            }
        ],
        "pageContext": {
            "pageId": "f5b1510e-3810-42ab-8135-8ffa5ef221ba",
            "isSeeAllPage": None
        },
        "gameSortStyle": None
    })

@GamesAPIRoute.route("/v1/games/list", methods=["GET"])
@limiter.limit("60/minute")
@auth.authenticated_required_api
def games_list():
    sortToken = request.args.get("sortToken", default = "MostPopular", type = str)
    startRows = request.args.get("startRows", default = 0, type = int)
    maxRows = request.args.get("maxRows", default = 40, type = int)

    if sortToken not in ["MostPopular", "Featured", "RecentlyUpdated"]:
        return jsonify( { "errors": [ { "code": 0, "message": "Invalid sort token." } ] } ), 400
    
    if startRows < 0:
        return jsonify( { "errors": [ { "code": 0, "message": "Invalid start rows." } ] } ), 400
    
    if maxRows > 40 or maxRows < 0:
        return jsonify( { "errors": [ { "code": 0, "message": "Max rows must be between 0-40" } ] } ), 400
    
    pageNumber = math.floor(startRows / maxRows) + 1

    if sortToken == "MostPopular":
        PopularGames = Universe.query.filter( and_( Universe.is_public == True, Universe.place_year == PlaceYear.Twenty ) ).outerjoin( Place, Place.parent_universe_id == Universe.id ).outerjoin( PlaceServer, PlaceServer.serverPlaceId == Place.placeid ).group_by( Universe.id )
        PopularGames = PopularGames.order_by( func.coalesce( func.sum( PlaceServer.playerCount ), 0 ).desc() ).order_by( func.coalesce( func.sum( Place.visitcount ), 0 ).desc() )
        
        UniverseObjsList = PopularGames.paginate(
            page = pageNumber,
            per_page = maxRows,
            error_out = False
        )
    elif sortToken == "Featured":
        FeaturedGames = Universe.query.filter( and_( Universe.is_public == True, Universe.place_year == PlaceYear.Twenty, Universe.is_featured == True ) ).outerjoin( Place, Place.parent_universe_id == Universe.id ).outerjoin( PlaceServer, PlaceServer.serverPlaceId == Place.placeid ).group_by( Universe.id )
        FeaturedGames = FeaturedGames.order_by( func.coalesce( func.sum( PlaceServer.playerCount ), 0 ).desc() ).order_by( func.coalesce( func.sum( Place.visitcount ), 0 ).desc() )
        UniverseObjsList = FeaturedGames.paginate(
            page = pageNumber,
            per_page = maxRows,
            error_out = False
        )
    else: # Fallback to RecentlyUpdated #sortToken == "RecentlyUpdated":
        RecentlyUpdatedGames = Universe.query.filter( and_( Universe.is_public == True, Universe.place_year == PlaceYear.Twenty )).order_by( Universe.updated_at.desc() )
        UniverseObjsList = RecentlyUpdatedGames.paginate(
            page = pageNumber,
            per_page = maxRows,
            error_out = False
        )

    UniverseObjsList : pagination.Pagination = UniverseObjsList
    
    GameList = []
    for UniverseObj in UniverseObjsList.items:
        UniverseObj : Universe
        PlaceObj : Place = Place.query.filter_by( placeid = UniverseObj.root_place_id ).first()
        Upvotes, Downvotes = GetAssetLikesAndDislikes(PlaceObj.placeid)
        PlaceAssetObj : Asset = PlaceObj.assetObj
        CreatorObj : User | Group = User.query.filter_by(id=UniverseObj.creator_id).first() if UniverseObj.creator_type == 0 else Group.query.filter_by(id=UniverseObj.creator_id).first()
        GameList.append({
            "creatorId": UniverseObj.creator_id,
            "creatorName": CreatorObj.username if UniverseObj.creator_type == 0 else CreatorObj.name,
            "creatorType": "User" if UniverseObj.creator_type == 0 else "Group",
            "creatorHasVerifiedBadge": False,
            "totalUpVotes": Upvotes,
            "totalDownVotes": Downvotes,
            "universeId": UniverseObj.id,
            "name": PlaceAssetObj.name,
            "placeId": PlaceObj.placeid,
            "playerCount": placeinfo.GetUniversePlayingCount(UniverseObj),
            "imageToken": "",
            "isSponsored": False,
            "nativeAdData": "",
            "isShowSponsoredLabel": False,
            "price": 0,
            "analyticsIdentifier": "",
            "gameDescription": PlaceAssetObj.description,
            "genre": "All",
            "minimumAge": 0
        })
    
    return jsonify({
        "games": GameList,
        "suggestedKeyword": "",
        "correctedKeyword": "",
        "filteredKeyword": "",
        "hasMoreRows": UniverseObjsList.has_next,
        "nextPageExclusiveStartId": 0,
        "featuredSearchUniverseId": 0,
        "emphasis": False,
        "cutOffIndex": 0,
        "algorithm": "",
        "algorithmQueryType": "",
        "suggestionAlgorithm": "",
        "relatedGames": []
    })

@GamesAPIRoute.route("/v1/games/<int:placeId>/votes", methods=["GET"])
@limiter.limit("60/minute")
@auth.authenticated_required_api
def get_place_votes( placeId : int ):
    PlaceObj : Place = Place.query.filter_by(placeid=placeId).first()
    if not PlaceObj:
        return jsonify( { "errors": [ { "code": 0, "message": "Invalid placeId." } ] } ), 400
    
    Upvotes, Downvotes = GetAssetLikesAndDislikes(PlaceObj.placeid)
    return jsonify({
        "id": PlaceObj.placeid,
        "upVotes": Upvotes,
        "downVotes": Downvotes
    })

@GamesAPIRoute.route("/v1/games/<int:placeId>/votes/user", methods=["GET"])
@limiter.limit("60/minute")
@auth.authenticated_required_api
def get_place_user_votes( placeId : int ):
    PlaceObj : Place = Place.query.filter_by(placeid=placeId).first()
    if not PlaceObj:
        return jsonify( { "errors": [ { "code": 0, "message": "Invalid placeId." } ] } ), 400
    
    return jsonify({
        "canVote": False,
        "userVote": False,
        "reasonForNotVoteable": "Voting is disabled"
    })

@GamesAPIRoute.route("/v1/games/multiget-playability-status", methods=["GET"])
@limiter.limit("60/minute")
@auth.authenticated_required_api
def get_playability_status():
    universeIdsList = request.args.get("universeIds", default = "", type = str)
    if universeIdsList == "":
        return jsonify( { "errors": [ { "code": 8, "message": "No universe IDs were specified." } ] } ), 400
    
    try:
        universeIdsList = universeIdsList.split(",")
        universeIdsList = [int(x) for x in universeIdsList]
        universeIdsList = list(set(universeIdsList))
    except:
        return jsonify( { "errors": [ { "code": 8, "message": "Invalid universe IDs were specified." } ] } ), 400
    
    if len(universeIdsList) > 100:
        return jsonify( { "errors": [ { "code": 9, "message": "Too many universe IDs were specified." } ] } ), 400
    
    RequestedData = []
    for universeId in universeIdsList:
        RequestedData.append({
            "playabilityStatus": 0,
            "isPlayable": True,
            "universeId": universeId
        })

    return jsonify(RequestedData), 200

@GamesAPIRoute.route("/v2/games/<int:universeId>/media", methods=["GET"])
@limiter.limit("60/minute")
@auth.authenticated_required_api
def get_game_media( universeId : int ):
    UniverseObj : Universe = Universe.query.filter_by(id = universeId).first()
    if not UniverseObj:
        return jsonify( { "errors": [ { "code": 2, "message": "The requested universe does not exist." } ] } ), 404
    return jsonify({"data": []}), 200

@GamesAPIRoute.route("/v1/games", methods=["GET"])
@limiter.limit("60/minute")
@auth.authenticated_required_api
def multi_get_game_info():
    universeIdsList = request.args.get("universeIds", default = "", type = str)
    if universeIdsList == "":
        return jsonify( { "errors": [ { "code": 8, "message": "No universe IDs were specified." } ] } ), 400
    
    try:
        universeIdsList = universeIdsList.split(",")
        universeIdsList = [int(x) for x in universeIdsList]
        universeIdsList = list(set(universeIdsList))
    except:
        return jsonify( { "errors": [ { "code": 8, "message": "Invalid universe IDs were specified." } ] } ), 400

    if len(universeIdsList) > 100:
        return jsonify( { "errors": [ { "code": 9, "message": "Too many universe IDs were specified." } ] } ), 400
    
    RequestedData = []
    for universeId in universeIdsList:
        UniverseObj : Universe = Universe.query.filter_by(id=universeId).first()
        if UniverseObj is None:
            continue
        PlaceObj : Place = Place.query.filter_by( placeid = UniverseObj.root_place_id ).first()
        if PlaceObj is None:
            continue

        PlaceAssetObj : Asset = PlaceObj.assetObj
        CreatorObj : User | Group = User.query.filter_by(id=UniverseObj.creator_id).first() if UniverseObj.creator_type == 0 else Group.query.filter_by(id=UniverseObj.creator_id).first()

        RequestedData.append({
            "id": UniverseObj.id,
            "rootPlaceId": UniverseObj.root_place_id,
            "name": PlaceAssetObj.name,
            "description": PlaceAssetObj.description,
            "sourceName": PlaceAssetObj.name,
            "sourceDescription": PlaceAssetObj.description,
            "creator": {
                "id": CreatorObj.id,
                "type": "User" if UniverseObj.creator_type == 0 else "Group",
                "name": CreatorObj.username if UniverseObj.creator_type == 0 else CreatorObj.name,
                "hasVerifiedBadge": False,
                "isRNVAccount": False
            },
            "price": 0,
            "allowedGearGenres": [],
            "allowedGearCategories": [],
            "isGenreEnforced": True,
            "copyingAllowed": False,
            "playing": placeinfo.GetUniversePlayingCount(UniverseObj),
            "visits": PlaceObj.visitcount,
            "maxPlayers": PlaceObj.maxplayers,
            "created": UniverseObj.created_at.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
            "updated": UniverseObj.updated_at.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
            "studioAccessToApisAllowed": False,
            "createVipServersAllowed": False,
            "universeAvatarType": 1,
            "genre": "All",
            "isAllGenre": True,
            "isFavoritedByUser": False,
            "favoritedCount": GetAssetFavoriteCount(PlaceObj.placeid)
        })

    return jsonify({
        "data": RequestedData
    }), 200

@GamesAPIRoute.route("/v1/games/<int:universeId>/favorites", methods=["GET"])
@limiter.limit("60/minute")
@auth.authenticated_required_api
def get_game_favorites( universeId : int ):
    UniverseObj : Universe = Universe.query.filter_by(id = universeId).first()
    if not UniverseObj:
        return jsonify( { "errors": [ { "code": 2, "message": "The requested universe does not exist." } ] } ), 404
    
    AuthenticatedUser : User = auth.GetCurrentUser()

    return jsonify({
        "isFavorited": GetUserFavoriteStatus( universeId, AuthenticatedUser.id)
    }), 200

@GamesAPIRoute.route("/v1/games/<int:universeId>/social-links/list", methods=["GET"])
@limiter.limit("60/minute")
@auth.authenticated_required_api
def get_universe_social_links_list( universeId : int ):
    UniverseObj : Universe = Universe.query.filter_by(id = universeId).first()
    if not UniverseObj:
        return jsonify( { "errors": [ { "code": 2, "message": "The requested universe does not exist." } ] } ), 404
    
    return jsonify({
        "data": []
    }), 200

@GamesAPIRoute.route("/v1/games/<int:universeId>/game-passes", methods=["GET"])
@limiter.limit("60/minute")
@auth.authenticated_required_api
def get_universe_game_passes( universeId : int ):
    UniverseObj : Universe = Universe.query.filter_by(id = universeId).first()
    if not UniverseObj:
        return jsonify( { "errors": [ { "code": 2, "message": "The requested universe does not exist." } ] } ), 404
    
    return jsonify({
        "previousPageCursor": None,
        "nextPageCursor": None,
        "data": []
    }), 200

@GamesAPIRoute.route("/v1/games/recommendations/game/<int:universeId>", methods=["GET"])
@limiter.limit("60/minute")
@auth.authenticated_required_api
def get_recommended_games_from_universeid( universeId : int ):
    UniverseObj : Universe = Universe.query.filter_by(id = universeId).first()
    if not UniverseObj:
        return jsonify( { "errors": [ { "code": 2, "message": "The requested universe does not exist." } ] } ), 404
    
    return jsonify({
        "games": [],
        "nextPaginationKey": None
    }), 200