from app.extensions import redis_controller, db
from app.util import redislock

from app.models.usereconomy import UserEconomy
from app.models.user import User
from app.models.userassets import UserAsset
from app.models.groups import Group, GroupEconomy
from app.models.asset import Asset
from app.models.asset_rap import AssetRap
from app.services.groups import GetGroupFromId, GetUserFromId

import redis_lock
import math

class InvalidCurrencyTypeException(Exception):
    pass
class EconomyLockAcquireException(Exception):
    pass
class InsufficientFundsException(Exception):
    pass
class AssetNotLimitedException(Exception):
    pass
class AssetDoesNotExistException(Exception):
    pass

def TaxCurrencyAmount( Amount : int ) -> int:
    return math.floor( Amount * 0.7 )

def GetAssetFromId( assetid : int | Asset ) -> Asset | None:
    """
        Returns the Asset object for the given assetid
    """
    if isinstance(assetid, Asset):
        return assetid
    AssetObj : Asset = Asset.query.filter_by(id=assetid).first()
    if AssetObj is None:
        raise AssetDoesNotExistException("Asset does not exist")
    return AssetObj

def GetUserEconomyObj( TargetUser : User ) -> UserEconomy | None:
    """
        Returns the UserEconomy object for the given user
    """
    return UserEconomy.query.filter_by( userid=TargetUser.id ).first()

def GetGroupEconomyObj( TargetGroup : Group ) -> GroupEconomy | None:
    """
        Returns the GroupEconomy object for the given group
    """
    return GroupEconomy.query.filter_by( group_id=TargetGroup.id ).first()

def GetUserBalance( TargetUser : User ) -> tuple[int, int]:
    """
        Returns the User's Robux and Tickets balance
    """
    EconomyObj : UserEconomy = GetUserEconomyObj( TargetUser )
    return EconomyObj.robux, EconomyObj.tix

def GetGroupBalance( TargetGroup : Group ) -> tuple[int, int]:
    """
        Returns the Group's Robux and Tickets balance
    """
    EconomyObj : GroupEconomy = GetGroupEconomyObj( TargetGroup )
    return EconomyObj.robux_balance, EconomyObj.tix_balance

def UnsafeIncrementTargetBalance( Target : User | Group, Amount : int, CurrencyType : int ): # CurrencyType is 0 for Robux, 1 for Tickets
    """
        Increments the Target Balance ( Not Recommended for normal use please instead use IncrementTargetBalance)
    """
    if isinstance(Target, User):
        TargetEconomyObj : UserEconomy = GetUserEconomyObj( Target )
        if CurrencyType == 0:
            TargetEconomyObj.robux += Amount
        elif CurrencyType == 1:
            TargetEconomyObj.tix += Amount
        else:
            raise InvalidCurrencyTypeException("Invalid Currency Type")
        db.session.commit()
    elif isinstance(Target, Group):
        TargetEconomyObj : GroupEconomy = GetGroupEconomyObj( Target )
        if CurrencyType == 0:
            TargetEconomyObj.robux_balance += Amount
        elif CurrencyType == 1:
            TargetEconomyObj.tix_balance += Amount
        else:
            raise InvalidCurrencyTypeException("Invalid Currency Type")
        db.session.commit()
    else:
        raise TypeError("Invalid Target Type")

def UnsafeDecrementTargetBalance( Target : User | Group, Amount : int, CurrencyType : int ): # CurrencyType is 0 for Robux, 1 for Tickets
    """
        Decrements the Target Balance ( Not Recommended for normal use please instead use DecrementTargetBalance)
    """
    if isinstance(Target, User):
        TargetEconomyObj : UserEconomy = GetUserEconomyObj( Target )
        if CurrencyType == 0:
            TargetEconomyObj.robux -= Amount
        elif CurrencyType == 1:
            TargetEconomyObj.tix -= Amount
        else:
            raise InvalidCurrencyTypeException("Invalid Currency Type")
        db.session.commit()
    elif isinstance(Target, Group):
        TargetEconomyObj : GroupEconomy = GetGroupEconomyObj( Target )
        if CurrencyType == 0:
            TargetEconomyObj.robux_balance -= Amount
        elif CurrencyType == 1:
            TargetEconomyObj.tix_balance -= Amount
        else:
            raise InvalidCurrencyTypeException("Invalid Currency Type")
        db.session.commit()
    else:
        raise TypeError("Invalid Target Type")
    
def IncrementTargetBalance( Target : User | Group, Amount : int, CurrencyType : int ): # CurrencyType is 0 for Robux, 1 for Tickets
    """
        Increments the Target Balance
    """
    if Amount < 0:
            raise ValueError("Amount must be positive")
    if isinstance(Target, User):
        with redis_lock.Lock( redis_client = redis_controller, name = f"economy:{Target.id}", expire = 1, auto_renewal = True ):
            UnsafeIncrementTargetBalance( Target, Amount, CurrencyType )
        return
    elif isinstance(Target, Group):
        with redis_lock.Lock( redis_client = redis_controller, name = f"economy_group:{Target.id}", expire = 1, auto_renewal = True ):
            UnsafeIncrementTargetBalance( Target, Amount, CurrencyType )
        return
    else:
        raise TypeError("Invalid Target Type")

def DecrementTargetBalance( Target : User | Group, Amount : int, CurrencyType : int ): # CurrencyType is 0 for Robux, 1 for Tickets
    """
        Decrements the Target Balance
    """
    if Amount < 0:
        raise ValueError("Amount must be positive")
    if isinstance(Target, User):
        with redis_lock.Lock( redis_client = redis_controller, name = f"economy:{Target.id}", expire = 1, auto_renewal = True ):
            TargetEconomyObj : UserEconomy = GetUserEconomyObj( Target )
            if CurrencyType == 0:
                if TargetEconomyObj.robux < Amount:
                    raise InsufficientFundsException("Insufficient Funds")
            elif CurrencyType == 1:
                if TargetEconomyObj.tix < Amount:
                    raise InsufficientFundsException("Insufficient Funds")
            UnsafeDecrementTargetBalance( Target, Amount, CurrencyType )
        return
    elif isinstance(Target, Group):
        with redis_lock.Lock( redis_client = redis_controller, name = f"economy_group:{Target.id}", expire = 1, auto_renewal = True ):
            TargetEconomyObj : GroupEconomy = GroupEconomy.query.filter_by( group_id=Target.id ).first()
            if CurrencyType == 0:
                if TargetEconomyObj.robux_balance < Amount:
                    raise InsufficientFundsException("Insufficient Funds")
            elif CurrencyType == 1:
                if TargetEconomyObj.tix_balance < Amount:
                    raise InsufficientFundsException("Insufficient Funds")
            UnsafeDecrementTargetBalance( Target, Amount, CurrencyType )
        return
    else:
        raise TypeError("Invalid Target Type")

def TransferFunds( Source : User | Group, Target : User | Group, Amount : int, CurrencyType : int, ApplyTax : bool = False): # CurrencyType is 0 for Robux, 1 for Tickets
    """
        Transfers funds from the source to the target
    """

    if Amount < 0:
        raise ValueError("Amount must be positive")
    if Source == Target:
        raise ValueError("Source and Target must be different")
    if CurrencyType not in [0,1]:
        raise InvalidCurrencyTypeException("Invalid Currency Type")
    if isinstance(Source, User):
        SourceEconomyObj : UserEconomy = GetUserEconomyObj( Source )
        if CurrencyType == 0:
            if SourceEconomyObj.robux < Amount:
                raise InsufficientFundsException("Insufficient Funds")
        elif CurrencyType == 1:
            if SourceEconomyObj.tix < Amount:
                raise InsufficientFundsException("Insufficient Funds")
    elif isinstance(Source, Group):
        SourceEconomyObj : GroupEconomy = GroupEconomy.query.filter_by( group_id=Source.id ).first()
        if CurrencyType == 0:
            if SourceEconomyObj.robux_balance < Amount:
                raise InsufficientFundsException("Insufficient Funds")
        elif CurrencyType == 1:
            if SourceEconomyObj.tix_balance < Amount:
                raise InsufficientFundsException("Insufficient Funds")
    else:
        raise TypeError("Invalid Source Type")

    TakenAmount : int = Amount
    GivenAmount : int = Amount
    if ApplyTax:
        GivenAmount = TaxCurrencyAmount( Amount )
    try:
        DecrementTargetBalance( Source, TakenAmount, CurrencyType )
    except InsufficientFundsException:
        raise InsufficientFundsException("Insufficient Funds")
    IncrementTargetBalance( Target, GivenAmount, CurrencyType )

    return

def AdjustAssetRap(AssetObj : Asset | int, robux : int):
    """ Adjusts the RAP of an asset
        https://roblox.fandom.com/wiki/Recent_Average_Price
        This will only work with assets that are limited
    """
    AssetObj : Asset = GetAssetFromId(AssetObj)

    AssetRapObject : AssetRap = AssetRap.query.filter_by(assetid=AssetObj.id).first()
    if AssetRapObject is None:
        if not AssetObj.is_limited:
            raise AssetNotLimitedException("Asset is not limited")
        AssetRapObject = AssetRap(assetid=AssetObj.id, rap=robux)
        db.session.add(AssetRapObject)
        db.session.commit()
        return True
    if AssetRapObject.rap <= 0:
        AssetRapObject.rap = robux
    CurrentRAP = AssetRapObject.rap
    AssetRapObject.rap = math.floor(CurrentRAP - ( CurrentRAP - robux ) / 10)
    db.session.commit()
    return True

def GetAssetRap(AssetObj : Asset | int ) -> int:
    """
        Returns the RAP of an asset
    """
    AssetObj : Asset = GetAssetFromId(AssetObj)
    if not AssetObj.is_limited:
        raise AssetNotLimitedException("Asset is not limited")

    AssetRapObject : AssetRap = AssetRap.query.filter_by(assetid=AssetObj.id).first()
    if AssetRapObject is None:
        AssetRapObject = AssetRap(assetid=AssetObj.id, rap=0)
        db.session.add(AssetRapObject)
        db.session.commit()
    return AssetRapObject.rap

def GetCreatorOfAsset( AssetObj : Asset | int ) -> User | Group | None:
    """
        Returns the creator of an asset
    """
    AssetObj : Asset = GetAssetFromId(AssetObj)
    if AssetObj.creator_type == 1:
        return GetGroupFromId(AssetObj.creator_id)
    elif AssetObj.creator_type == 0:
        return GetUserFromId(AssetObj.creator_id)
    else:
        return None
    
def CalculateUserRAP( UserObj : User | int, skipCache : bool = False ) -> int:
    """
        Calculates the RAP of a user
    """
    if redis_controller.exists(f"rap_calculation:{UserObj.id}") and not skipCache:
        return int(redis_controller.get(f"rap_calculation:{UserObj.id}"))

    UserObj : User = GetUserFromId(UserObj)
    UserRAP : int = 0
    UserLimitedAssets : list[UserAsset] = UserAsset.query.filter_by(userid=UserObj.id).outerjoin( Asset, Asset.id == UserAsset.assetid ).filter( Asset.is_limited == True ).all()

    for UserLimitedAsset in UserLimitedAssets:
        UserRAP += GetAssetRap(UserLimitedAsset.assetid)

    redis_controller.set(f"rap_calculation:{UserObj.id}", UserRAP, ex = 60)
    return UserRAP