from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response, jsonify
from app.extensions import db, redis_controller, csrf
import uuid
import json
import requests
import hashlib
import logging
import os
import base64
from datetime import datetime

from app.services.gameserver_comm import perform_post
from app.util import websiteFeatures, s3helper

from app.models.gameservers import GameServer
from app.models.asset_version import AssetVersion
from app.models.asset_thumbnail import AssetThumbnail
from app.models.user_thumbnail import UserThumbnail
from app.models.place_icon import PlaceIcon
from app.models.asset import Asset
from app.models.package_asset import PackageAsset
from app.models.place import Place
from app.models.user_avatar import UserAvatar
from app.models.user_avatar_asset import UserAvatarAsset

from app.enums.PlaceYear import PlaceYear

Thumbnailer = Blueprint('thumbnailer', __name__, url_prefix='/internal')
AssetTypetoThumbnailRenderType = {
    4 : 4,
    8 : 11,
    9 : 5,
    10 : 3,
    11 : 15,
    12 : 14,
    1 : 6,
    13 : 6,
    18 : 6,
    19 : 12,
    2 : 7,
    17 : 8,
    27 : 9,
    28 : 9,
    29 : 9,
    30 : 9,
    31 : 9,
    32 : 17,
    41 : 11,
    42 : 11,
    43 : 11,
    44 : 11,
    45 : 11,
    46 : 11,
    47 : 11,
    40 : 13,
}

"""
    omg was i fucking high or something several months ago i gotta rewrite this one day
    - something.else 19/2/2024
"""

def GetAvatarHash( userid : int ) -> str:
    """
    Returns the avatar hash for the userid provided
    """
    UserAvatarObject : UserAvatar = UserAvatar.query.filter_by(user_id=userid).first()
    if UserAvatarObject is None:
        return None
    UserAvatarAssetObject : UserAvatarAsset = UserAvatarAsset.query.filter_by(user_id=userid).all()
    HashString = ""
    for UserAvatarAssetObject in UserAvatarAssetObject:
        HashString += str(UserAvatarAssetObject.asset_id)+"-"
    HashString += str(UserAvatarObject.head_color_id)+"-"
    HashString += str(UserAvatarObject.torso_color_id)+"-"
    HashString += str(UserAvatarObject.left_arm_color_id)+"-"
    HashString += str(UserAvatarObject.right_arm_color_id)+"-"
    HashString += str(UserAvatarObject.left_leg_color_id)+"-"
    HashString += str(UserAvatarObject.right_leg_color_id)+'-'
    HashString += str(UserAvatarObject.r15)+'-'
    HashString += str(UserAvatarObject.height_scale)+'-'
    HashString += str(UserAvatarObject.width_scale)+'-'
    HashString += str(UserAvatarObject.head_scale)+'-'
    HashString += str(UserAvatarObject.proportion_scale)+'-'
    HashString += str(UserAvatarObject.body_type_scale)

    AvatarHash = hashlib.sha256(HashString.encode()).hexdigest()
    return AvatarHash


def findBestThumbnailer() -> GameServer | None:
    weight_ping_time = 3
    weight_queue_size = 0.3
    
    AllGameServers : list[GameServer] = GameServer.query.filter_by(allowThumbnailGen=True, isRCCOnline=True).filter( GameServer.thumbnailQueueSize < 40 ).all()
    if len(AllGameServers) == 0:
        return None
    BestGameServer : GameServer = None
    for GameServerObject in AllGameServers:
        GameServerObject.score = (weight_ping_time * GameServerObject.heartbeatResponseTime) + (weight_queue_size * GameServerObject.thumbnailQueueSize)
        if BestGameServer is None:
            BestGameServer = GameServerObject
            continue
        if GameServerObject.score < BestGameServer.score:
            BestGameServer = GameServerObject
    #logging.info(f"GameserverLoadBalancer: {str(BestGameServer.serverId)} - {str(BestGameServer.score)} - Ping: {str(round(BestGameServer.heartbeatResponseTime, 3))}secs - Queue: {str(BestGameServer.thumbnailQueueSize)}")
    return BestGameServer

def TakeUserThumbnail(UserId: int, bypassCooldown=False, bypassCache=False, Is3D=False):
    """
    Takes a thumbnail and headshot for the userid provided
    bypassCooldown: Bypasses the 5 second cooldown for that user
    bypassCache: Bypasses the cache and takes a new thumbnail and headshot
    Is3D: Whether to request a 3D thumbnail
    """
    if redis_controller.get(f"Thumbnailer:UserId:{UserId}:Taken") is not None and not bypassCooldown:
        return "Thumbnail Attempted Recently"
    redis_controller.set(f"Thumbnailer:UserId:{UserId}:Taken", "True", 5)
    
    ImageHeadshotCache = None
    ImageThumbnailCache = None
    Image3DThumbnailCache = None
    AvatarHash = GetAvatarHash(UserId)
    
    if not bypassCache:
        UserThumbnailObject = UserThumbnail.query.filter_by(userid=UserId).first()
        if UserThumbnailObject is None:
            UserThumbnailObject = UserThumbnail(userid=UserId, full_contenthash=None, headshot_contenthash=None, full_3dcontenthash=None, updated_at=datetime.utcnow())
            db.session.add(UserThumbnailObject)
            
        ImageThumbnailCache = redis_controller.get(f"Thumbnailer:UserImage:{AvatarHash}:Thumbnail")
        if ImageThumbnailCache is not None:
            UserThumbnailObject.full_contenthash = ImageThumbnailCache
            
        ImageHeadshotCache = redis_controller.get(f"Thumbnailer:UserImage:{AvatarHash}:Headshot")
        if ImageHeadshotCache is not None:
            UserThumbnailObject.headshot_contenthash = ImageHeadshotCache
            
        Image3DThumbnailCache = redis_controller.get(f"Thumbnailer:UserImage:{AvatarHash}:3DThumbnail")
        if Image3DThumbnailCache is not None and Is3D:
            UserThumbnailObject.full_3dcontenthash = Image3DThumbnailCache
            
        db.session.commit()
        
        if ImageThumbnailCache is not None and ImageHeadshotCache is not None:
            if not Is3D or (Is3D and Image3DThumbnailCache is not None):
                return
                
    if redis_controller.get(f"Thumbnailer:AvatarHash:{AvatarHash}:Lock") is not None:
        return "Thumbnail Attempted Recently"
    redis_controller.set(f"Thumbnailer:AvatarHash:{AvatarHash}:Lock", "True", 5)

    if not websiteFeatures.GetWebsiteFeature("ThumbnailRendering"):
        return "Thumbnail Rendering is disabled"
        
    GameServerObject: GameServer = findBestThumbnailer()
    if GameServerObject is None:
        return "No suitable game servers found"
        
    requests_to_send = 0
    UserAvatarObj: UserAvatar = UserAvatar.query.filter_by(user_id=UserId).first()
    
    if (not bypassCache and ImageThumbnailCache is None) or bypassCache:
        ThumbnailReqId = str(uuid.uuid4())
        redis_controller.set(f"Thumbnailer:Request:{ThumbnailReqId}", json.dumps({
            "UserId": UserId,
            "Type": 0,
            "Is3D": False
        }), ex=600)
        
        try:
            perform_post(
                TargetGameserver=GameServerObject,
                Endpoint="Thumbnail",
                JSONData={
                    "type": 16 if UserAvatarObj.r15 else 0,
                    "userid": UserId,
                    "reqid": ThumbnailReqId,
                    "image_x": 768,
                    "image_y": 768,
                    "Is3D": False
                },
                RequestTimeout=0.5
            )
            requests_to_send += 1
        except Exception as e:
            logging.error(f"Error requesting 2D thumbnail for UserId {UserId}: {str(e)}")
    
    if (not bypassCache and ImageHeadshotCache is None) or bypassCache:
        HeadshotReqId = str(uuid.uuid4())
        redis_controller.set(f"Thumbnailer:Request:{HeadshotReqId}", json.dumps({
            "UserId": UserId,
            "Type": 1,
            "Is3D": False
        }), ex=600)
        
        try:
            perform_post(
                TargetGameserver=GameServerObject,
                Endpoint="Thumbnail",
                JSONData={
                    "type": 1,
                    "userid": UserId,
                    "reqid": HeadshotReqId,
                    "image_x": 768,
                    "image_y": 768,
                    "Is3D": False
                },
                RequestTimeout=0.5
            )
            requests_to_send += 1
        except Exception as e:
            logging.error(f"Error requesting headshot for UserId {UserId}: {str(e)}")
    
    if Is3D and ((not bypassCache and Image3DThumbnailCache is None) or bypassCache):
        ThreeDReqId = str(uuid.uuid4())
        redis_controller.set(f"Thumbnailer:Request:{ThreeDReqId}", json.dumps({
            "UserId": UserId,
            "Type": 0, 
            "Is3D": True
        }), ex=600)
        
        try:
            perform_post(
                TargetGameserver=GameServerObject,
                Endpoint="Thumbnail",
                JSONData={
                    "type": 16 if UserAvatarObj.r15 else 0,
                    "userid": UserId,
                    "reqid": ThreeDReqId,
                    "image_x": 768,
                    "image_y": 768,
                    "Is3D": True
                },
                RequestTimeout=0.5
            )
            requests_to_send += 1
        except Exception as e:
            logging.error(f"Error requesting 3D thumbnail for UserId {UserId}: {str(e)}")
    
    if requests_to_send > 0:
        GameServerObject.thumbnailQueueSize += requests_to_send
        db.session.commit()
        
    return "Thumbnail request sent"

def ValidatePlaceFileRequest(PlaceId : int, RequestId : str = None, AssetYear : PlaceYear = None ) -> None:
    """
        Sends a validate file request to a thumbnailer server
    """
    if not websiteFeatures.GetWebsiteFeature("AssetValidationService"):
        redis_controller.set(f"ValidatePlaceFileRequest:{RequestId}", json.dumps({
            "valid": False,
            "error": "Asset Validation Service is temporarily disabled, try again later"
        }), ex=600)

    GameServerObject : GameServer = findBestThumbnailer()
    if GameServerObject is None:
        redis_controller.set(f"ValidatePlaceFileRequest:{RequestId}", json.dumps({
            "valid": False,
            "error": "Cannot verify file at this time, try again later"
        }), ex=600)
        return
    if AssetYear is None:
        PlaceObj : Place = Place.query.filter_by(placeid=PlaceId).first()
        AssetYear = PlaceYear.Eighteen
        if PlaceObj is not None:
            AssetYear = PlaceObj.placeyear
    try:
        AssetYearToEndpoint = {
            PlaceYear.Eighteen: "AssetValidation2018",
            PlaceYear.Sixteen: "AssetValidation2016",
            PlaceYear.Fourteen: "AssetValidation2016",
            PlaceYear.Twenty: "AssetValidation2020",
            PlaceYear.TwentyOne: "AssetValidation2021"
        }
        if AssetYear not in AssetYearToEndpoint:
            redis_controller.set(f"ValidatePlaceFileRequest:{RequestId}", json.dumps({
                "valid": False,
                "error": "Invalid place year"
            }), ex=600)
            return
        
        response = perform_post(
            TargetGameserver = GameServerObject,
            Endpoint = AssetYearToEndpoint[AssetYear],
            JSONData = {
                "assetid": PlaceId
            },
            RequestTimeout = 60
        )

        response.raise_for_status()
        response = response.json()
        if response["valid"]:
            redis_controller.set(f"ValidatePlaceFileRequest:{RequestId}", json.dumps({
                "valid": True,
                "error": None
            }), ex=600)
            return
        else:
            redis_controller.set(f"ValidatePlaceFileRequest:{RequestId}", json.dumps({
                "valid": False,
                "error": response["reason"]
            }), ex=600)
            return
    except Exception as e:
        logging.error(f"Error while trying to validate place file: {str(e)}")
        redis_controller.set(f"ValidatePlaceFileRequest:{RequestId}", json.dumps({
            "valid": False,
            "error": "An error occured while trying to validate the place file"
        }), ex=600)
        return

def TakeThumbnail(AssetId : int, isIcon = False, bypassCooldown = False, bypassCache = False): # isIcon only used for game icons
    """
    Takes a thumbnail for the assetid provided
    bypassCooldown: Bypasses the 2 minute cooldown for that asset
    """
    from app.routes.asset import GenerateTempAuthToken
    if redis_controller.get(f"Thumbnailer:AssetId:{AssetId}:{str(isIcon)}:Taken") is not None and not bypassCooldown:
        return "Thumbnail Attempted Recently"
    redis_controller.set(f"Thumbnailer:AssetId:{AssetId}:{str(isIcon)}:Taken", "True", 120)
    AssetObject : Asset = Asset.query.filter_by(id=AssetId).first()
    AssetVersionObject : AssetVersion = AssetVersion.query.filter_by(asset_id=AssetId).order_by(AssetVersion.version.desc()).first()
    if AssetVersionObject is None and AssetObject.asset_type.value != 32:
        return "Asset version not found"
    if not bypassCache:
        if not isIcon:
            ImageThumbnailCache = redis_controller.get(f"Thumbnailer:AssetImage:{AssetVersionObject.content_hash}:Thumbnail")
            if ImageThumbnailCache is not None:
                ThumbnailObject : AssetThumbnail = AssetThumbnail.query.filter_by(asset_id=AssetId, asset_version_id=AssetVersionObject.version).first()
                if ThumbnailObject is None:
                    AssetModeration = 1
                    if AssetObject.roblox_asset_id is not None:
                        AssetModeration = 0
                    ThumbnailObject = AssetThumbnail(
                        asset_id=AssetId,
                        asset_version_id=AssetVersionObject.version,
                        content_hash=ImageThumbnailCache,
                        moderation_status=AssetModeration,
                        created_at=datetime.utcnow()
                    )
                    db.session.add(ThumbnailObject)
                    try:
                        db.session.commit()
                    except:
                        return
                    return
                if ThumbnailObject.content_hash == ImageThumbnailCache:
                    return
                ThumbnailObject.content_hash = ImageThumbnailCache
                ThumbnailObject.moderation_status = 1
                try:
                    db.session.commit()
                except:
                    return
                return
        else:
            ImageIconCache = redis_controller.get(f"Thumbnailer:AssetImage:{AssetVersionObject.content_hash}:PlaceIcon")
            if ImageIconCache is not None:
                PlaceIconObject : PlaceIcon = PlaceIcon.query.filter_by(placeid=AssetId).first()
                if PlaceIconObject is None:
                    PlaceIconObject = PlaceIcon(
                        placeid=AssetId,
                        contenthash=ImageIconCache,
                        updated_at=datetime.utcnow(),
                        moderation_status=1, # Pending
                    )
                    db.session.add(PlaceIconObject)
                    db.session.commit()
                    return
                if PlaceIconObject.contenthash == ImageIconCache:
                    return
                PlaceIconObject.contenthash = ImageIconCache
                PlaceIconObject.moderation_status = 1
                PlaceIconObject.updated_at = datetime.utcnow()
                db.session.commit()
                return
    
    AssetObject : Asset = Asset.query.filter_by(id=AssetId).first()
    if AssetObject is None:
        return "Asset not found"
    AssetType = AssetObject.asset_type.value
    ThumbnailType = AssetTypetoThumbnailRenderType.get(AssetType)
    if ThumbnailType is None:
        if AssetType in[39, 3, 24, 5, 48,49,50,51,52,53,54,55,56]: # SolidModel, Animation, Lua and Audio
            if AssetType == 39:
                StaticImage = open("./app/files/NoRender.png", "rb").read()
            elif AssetType == 3:
                StaticImage = open("./app/files/AudioThumbnail.png", "rb").read()
            elif AssetType in [24,48,49,50,51,52,53,54,55,56]:
                StaticImage = open("./app/files/AnimationThumbnail.png", "rb").read()
            elif AssetType == 5:
                StaticImage = open("./app/files/LuaThumbnail.png", "rb").read()
            ImageHash = hashlib.sha512(StaticImage).hexdigest()
            if not s3helper.DoesKeyExist(ImageHash):
                s3helper.UploadBytesToS3(StaticImage, ImageHash, contentType="image/png")
            try:
                NewThumbnailObject : AssetThumbnail = AssetThumbnail(
                    asset_id=AssetId,
                    asset_version_id=AssetVersionObject.version,
                    content_hash=ImageHash,
                    moderation_status=0,
                    created_at=datetime.utcnow()
                )
                db.session.add(NewThumbnailObject)
                db.session.commit()
            except:
                pass
            return "Used Static Image"

        return "Thumbnail type not found"
    
    if not websiteFeatures.GetWebsiteFeature("ThumbnailRendering"):
        return "Thumbnail Rendering is disabled"

    GameServerObject : GameServer = findBestThumbnailer()
    if GameServerObject is None:
        return "No suitable game servers found"
    
    ThumbnailReqId = str(uuid.uuid4())
    redis_controller.set(f"Thumbnailer:Request:{ThumbnailReqId}", json.dumps({
        "AssetId": AssetId,
        "AssetVersionId": AssetVersionObject.version,
        "isIcon": isIcon,
        "AssetType": AssetType,
    }), ex=600)
    TargetX = 1024
    TargetY = 1024
    PlaceAuthorisationToken = None
    if AssetType == 9:
        PlaceAuthorisationToken = GenerateTempAuthToken( AssetId, Expiration = 600, CreatorIP = None )
    if AssetType == 9 and not isIcon:
        TargetX = 1280
        TargetY = 720
    if AssetType == 1: # Image
        TargetX = 256
        TargetY = 256
    if AssetType == 32: # Package
        AllPackageAssets : list[PackageAsset] = PackageAsset.query.filter_by(package_asset_id=AssetId).all()
        AssetId = ""
        for i in range(len(AllPackageAssets)):
            AssetId += f"https://www.nexium.fit/asset/?id={str(AllPackageAssets[i].asset_id)}"
            if i != len(AllPackageAssets) - 1:
                AssetId += ";"
    try:
        logging.info(f"thumbnailer : TakeThumbnail : Sent request to thumbnailer for asset {AssetId} with type {ThumbnailType} to {GameServerObject.serverName} [ {GameServerObject.serverId} ]")
        perform_post(
            TargetGameserver = GameServerObject,
            Endpoint = "Thumbnail",
            JSONData = {
                "type": ThumbnailType,
                "asset": AssetId,
                "reqid": ThumbnailReqId,
                "image_x": TargetX,
                "image_y": TargetY,
                "placetoken": PlaceAuthorisationToken
            }
        )
        GameServerObject.thumbnailQueueSize += 1
        db.session.commit()
    except Exception as e:
        return str(e)
    return "Thumbnail request sent"
    

def isValidAuthorizationToken( authtoken : str) -> GameServer:
    if authtoken is None:
        return None
    GameServerObject = GameServer.query.filter_by(accessKey=authtoken).first()
    return GameServerObject

import base64
def Handle3DObjectData(EncodedData: bytes) -> str:
    """
    Process the 3D object data from the thumbnailer and return a hash
    """
    try:
        Data = json.loads(EncodedData)

        TextureHashes = []
        OBJBytes = None
        MTLBytes = None
        OBJHash = None
        MTLHash = None

        for FileName, FileDict in Data["files"].items():
            DecodedData: bytes = base64.b64decode(FileDict["content"])

            if FileName.endswith(".obj"):
                OBJBytes = DecodedData
                OBJHash = f"{hashlib.sha512(DecodedData).hexdigest()}"
                s3helper.UploadBytesToS3(DecodedData, OBJHash, contentType="text/plain")
            elif FileName.endswith(".mtl"):
                MTLBytes = DecodedData
                MTLHash = f"{hashlib.sha512(DecodedData).hexdigest()}"
            else:
                TextureHash = f"{hashlib.sha512(DecodedData).hexdigest()}"
                TextureHashes.append(TextureHash)
                s3helper.UploadBytesToS3(DecodedData, TextureHash, contentType="image/png")
                
                if MTLBytes:
                    MTLBytes = MTLBytes.replace(FileName.encode(), f"{TextureHash}".encode())

        if MTLBytes:
            s3helper.UploadBytesToS3(MTLBytes, MTLHash, contentType="text/plain")

        OutputData = {
            "camera": Data["camera"],
            "aabb": Data["AABB"],
            "obj": OBJHash,
            "mtl": MTLHash,
            "textures": TextureHashes
        }
        
        JsonContent = json.dumps(OutputData).encode()
        # bruh
        ContentHash = f"{hashlib.sha512(JsonContent).hexdigest()}"
        
        s3helper.UploadBytesToS3(JsonContent, ContentHash, contentType="application/json")
        
        return ContentHash
    except Exception as e:
        logging.error(f"Error in Handle3DObjectData: {str(e)}")
        return None

@Thumbnailer.route('/thumbnailreturn', methods=["POST"])
@csrf.exempt
def thumbnailreturn():
    AuthorizationToken = request.headers.get("Authorization")
    if AuthorizationToken is None:
        return jsonify({"status": "error", "message": "Invalid authorization token"}), 400
    
    ThumbnailerOwner: GameServer = isValidAuthorizationToken(AuthorizationToken)
    if ThumbnailerOwner is None:
        return jsonify({"status": "error", "message": "Invalid authorization token"}), 400
    
    ReqId = request.headers.get("ReturnUUID")
    if ReqId is None:
        return jsonify({"status": "error", "message": "Invalid request id"}), 400

    RequestData = redis_controller.get(f"Thumbnailer:Request:{ReqId}")    
    if RequestData is None:
        return jsonify({"status": "error", "message": "Invalid request id"}), 400
    
    redis_controller.delete(f"Thumbnailer:Request:{ReqId}")
    RequestData = json.loads(RequestData)
    
    if "UserId" not in RequestData:
        AssetId = RequestData["AssetId"]
        AssetVersionId = RequestData["AssetVersionId"]
        isIcon = RequestData["isIcon"]
        AssetType = RequestData["AssetType"]

        ImageData = request.data
        ImageHash = hashlib.sha512(ImageData).hexdigest()

        s3helper.UploadBytesToS3(ImageData, ImageHash, contentType="image/png")
        AssetVersionObject: AssetVersion = AssetVersion.query.filter_by(asset_id=AssetId, version=AssetVersionId).first()
        if isIcon and AssetType == 9:
            PlaceIconObject: PlaceIcon = PlaceIcon.query.filter_by(placeid=AssetId).first()
            if PlaceIconObject is None:
                PlaceIconObject = PlaceIcon(placeid=AssetId, contenthash=ImageHash, updated_at=datetime.utcnow())
                db.session.add(PlaceIconObject)
            else:
                PlaceIconObject.contenthash = ImageHash
                PlaceIconObject.updated_at = datetime.utcnow()
                PlaceIconObject.moderation_status = 1
            if AssetVersionObject:
                redis_controller.setex(f"Thumbnailer:AssetImage:{AssetVersionObject.content_hash}:PlaceIcon", 60 * 60 * 24 * 3, ImageHash)
            try:
                ThumbnailerOwner.thumbnailQueueSize -= 1
                db.session.commit()
            except:
                pass
            return jsonify({"status": "success", "message": "Thumbnail saved"}), 200
        
        if AssetVersionObject:
            redis_controller.setex(f"Thumbnailer:AssetImage:{AssetVersionObject.content_hash}:Thumbnail", 60 * 60 * 24 * 3, ImageHash)
        
        ThumbnailObject: AssetThumbnail = AssetThumbnail.query.filter_by(asset_id=AssetId, asset_version_id=AssetVersionId).first()
        if ThumbnailObject is not None:
            ThumbnailObject.content_hash = ImageHash
            ThumbnailObject.updated_at = datetime.utcnow()
            ThumbnailerOwner.thumbnailQueueSize -= 1
            db.session.commit()
            return jsonify({"status": "success", "message": "Thumbnail saved"}), 200
        
        AssetObject: Asset = Asset.query.filter_by(id=AssetId).first()
        AssetModeration = 1
        if AssetObject.roblox_asset_id is not None:
            AssetModeration = 0
        
        AssetThumbnailObject = AssetThumbnail(asset_id=AssetId, asset_version_id=AssetVersionId, content_hash=ImageHash, created_at=datetime.utcnow(), moderation_status=AssetModeration) # 0 = Approved, 1 = Pending, 2 = Denied
        db.session.add(AssetThumbnailObject)
        db.session.commit()

        return jsonify({"status": "success", "message": "Thumbnail saved"}), 200
    else:
        UserId = RequestData["UserId"]
        ThumbnailType = RequestData["Type"]
        ThumbnailIs3D = RequestData.get("Is3D", False)
        ImageData = request.data
        ImageHash = None
        
        AvatarHash = GetAvatarHash(UserId)
        
        if ThumbnailIs3D:
            try:
                ContentHash = Handle3DObjectData(ImageData)
                if ContentHash is None:
                    return jsonify({"status": "error", "message": "Failed to process 3D data"}), 500
                
                redis_controller.setex(f"Thumbnailer:UserImage:{AvatarHash}:3DThumbnail", 60 * 60 * 24 * 3, ContentHash)
                ImageHash = ContentHash
            except Exception as e:
                logging.error(f"Error processing 3D avatar data: {str(e)}")
                return jsonify({"status": "error", "message": f"Error processing 3D data: {str(e)}"}), 500
        else:
            ImageHash = hashlib.sha512(ImageData).hexdigest()
            s3helper.UploadBytesToS3(ImageData, ImageHash, contentType="image/png")
            
            if ThumbnailType == 0: 
                redis_controller.setex(f"Thumbnailer:UserImage:{AvatarHash}:Thumbnail", 60 * 60 * 24 * 3, ImageHash)
            elif ThumbnailType == 1: 
                redis_controller.setex(f"Thumbnailer:UserImage:{AvatarHash}:Headshot", 60 * 60 * 24 * 3, ImageHash)
        
        UserThumbnailObject = UserThumbnail.query.filter_by(userid=UserId).first()
        if UserThumbnailObject is None:
            if ThumbnailType == 0:
                if ThumbnailIs3D:
                    UserThumbnailObject = UserThumbnail(
                        userid=UserId, 
                        full_contenthash=None, 
                        headshot_contenthash=None, 
                        full_3dcontenthash=ImageHash,
                        updated_at=datetime.utcnow()
                    )
                else:
                    UserThumbnailObject = UserThumbnail(
                        userid=UserId, 
                        full_contenthash=ImageHash, 
                        headshot_contenthash=None,
                        full_3dcontenthash=None,
                        updated_at=datetime.utcnow()
                    )
            else:
                UserThumbnailObject = UserThumbnail(
                    userid=UserId, 
                    headshot_contenthash=ImageHash, 
                    full_contenthash=None,
                    full_3dcontenthash=None,
                    updated_at=datetime.utcnow()
                )
            db.session.add(UserThumbnailObject)
        else:
            if ThumbnailType == 0: 
                if ThumbnailIs3D:
                    UserThumbnailObject.full_3dcontenthash = ImageHash
                else:
                    UserThumbnailObject.full_contenthash = ImageHash
            else:  
                UserThumbnailObject.headshot_contenthash = ImageHash
            
            UserThumbnailObject.updated_at = datetime.utcnow()
        
        ThumbnailerOwner.thumbnailQueueSize -= 1
        db.session.commit()
        return jsonify({"status": "success", "message": "Thumbnail updated"}), 200