# Commands for flask shell
from app.extensions import db, redis_controller
from app.models.user import User
from app.util import auth
from sqlalchemy import func
import logging
# henlo from china!
def lookup_user_id():
    """Lookup user by Id"""
    try:
        UserId : int = int(input("User Lookup by UserId: "))
        if UserId < 0:
            raise Exception("UserId must be a positive integer")
    except Exception as e:
        logging.error(f"Unable to parse user input, please enter a valid integer, error: {e}")
    
    UserObj : User = User.query.filter_by(id=UserId).first()
    if UserObj is None:
        logging.error(f"Unable to find user with UserId: {UserId}")
    else:
        logging.info(f"""
Username : {UserObj.username}
UserId : {UserObj.id}
CreatedOn : {UserObj.created}
LastPing : {UserObj.lastonline}
AccountStatus : {UserObj.accountstatus}
2FA Enabled : {UserObj.TOTPEnabled}

-- Description --
{UserObj.description}
-- End Description --

""")

def lookup_user_name():
    """Lookup user by Username"""
    try:
        Username : str = str(input("User Lookup by Username: "))
        if len(Username) < 1:
            raise Exception("Username must be a valid string")
    except Exception as e:
        logging.error(f"Unable to parse user input, please enter a valid string, error: {e}")
    
    UserObj : User = User.query.filter(func.lower(User.username) == func.lower(Username)).first()
    if UserObj is None:
        logging.error(f"Unable to find user with Username: {Username}")
    else:
        logging.info(f"""
Username : {UserObj.username}
UserId : {UserObj.id}
CreatedOn : {UserObj.created}
LastPing : {UserObj.lastonline}
AccountStatus : {UserObj.accountstatus}
2FA Enabled : {UserObj.TOTPEnabled}

-- Description --
{UserObj.description}
-- End Description --

""")
        
def refund_unused_invite_keys():
    from app.models.invite_key import InviteKey
    from app.services.economy import IncrementTargetBalance
    from app.util.transactions import CreateTransaction
    from app.enums.TransactionType import TransactionType
    from app.pages.messages.messages import CreateSystemMessage

    allDistinctInviteKeyCreators : list[User] = User.query.join(InviteKey, User.id == InviteKey.created_by).filter(InviteKey.used_by == None).distinct(User.id).all()

    def process_user_invite_keys( userObj : User ):
        AmountOwed : int = 0
        InviteKeysDeleted : int = 0

        AllInviteKeys : list[InviteKey] = InviteKey.query.filter_by(created_by=userObj.id, used_by=None).all()
        for InviteKeyObj in AllInviteKeys:
            AmountOwed += 20
            InviteKeysDeleted += 1
            db.session.delete(InviteKeyObj)
        db.session.commit()

        if AmountOwed > 0:
            IncrementTargetBalance(userObj, AmountOwed, 0)
            CreateTransaction(
                Reciever = userObj,
                Sender = None,
                CurrencyAmount = AmountOwed,
                CurrencyType = 0,
                TransactionType = TransactionType.BuildersClubStipend,
                AssetId = None,
                CustomText = f"Refunded {InviteKeysDeleted} unused invite keys",
            )
            CreateSystemMessage(
                subject = "Invite Key Refund",
                message = f"""Hello {userObj.username},
    This is an automated message to inform you that as invite keys are no longer used on NEXIUM, we have refunded you R$ {AmountOwed} for {InviteKeysDeleted} unused invite keys. Please contact us on our Discord Server if you have any questions.

Sincerely,
The NEXIUM Team""",
                userid = userObj.id
            )

            logging.info(f"Refunded {InviteKeysDeleted} unused invite keys for user {userObj.username}")
    
    logging.info(f"Found {len(allDistinctInviteKeyCreators)} users with unused invite keys")
    for UserObj in allDistinctInviteKeyCreators:
        process_user_invite_keys(UserObj)

def refund_limiteds():
    from app.models.userassets import UserAsset
    from app.models.asset import Asset
    from app.models.user_trade_items import UserTradeItem
    from app.services.economy import IncrementTargetBalance, GetAssetRap
    from app.util.transactions import CreateTransaction
    from app.enums.TransactionType import TransactionType
    from app.enums.MembershipType import MembershipType
    from app.pages.messages.messages import CreateSystemMessage
    from app.models.user import User
    from app.util.membership import GetUserMembership
    import math
    import logging
    from app import db

    limitedRAPValueLookup: dict[int, int] = {}
    refundCapAmount: dict[MembershipType, int] = {
        MembershipType.NonBuildersClub: 200,
        MembershipType.BuildersClub: 500,
        MembershipType.TurboBuildersClub: 750,
        MembershipType.OutrageousBuildersClub: 1200,
    }

    def _get_limited_rap_value(assetId: int):
        if assetId in limitedRAPValueLookup:
            return limitedRAPValueLookup[assetId]
        else:
            rapValue: int = GetAssetRap(assetId)
            limitedRAPValueLookup[assetId] = rapValue
            return rapValue

    def refund_user_limiteds(userObj: User):
        WipedAssets: list = []
        AllLimitedUserAssets: list[UserAsset] = (
            UserAsset.query.filter_by(userid=userObj.id)
            .join(Asset, UserAsset.assetid == Asset.id)
            .filter(Asset.is_limited == True)
            .all()
        )
        AmountOwed: int = 0

        for UserAssetObj in AllLimitedUserAssets:
            AssetObj: Asset = UserAssetObj.asset

            LimitedRAPValue: int = _get_limited_rap_value(UserAssetObj.assetid)
            AssetOriginalPrice: int = (
                AssetObj.price_robux
                if AssetObj.price_robux > 0
                else AssetObj.price_tix
            )

            if not AssetObj.price_robux > 0 and AssetOriginalPrice > 0:
                AssetOriginalPrice = math.floor(AssetOriginalPrice / 10)

            RefundedAmount: int = max(
                AssetOriginalPrice
                if AssetOriginalPrice > LimitedRAPValue
                else LimitedRAPValue,
                50,
            )
            AmountOwed += RefundedAmount

            WipedAssets.append(
                f"   - {AssetObj.name} [UAID: {UserAssetObj.id} / Serial: {UserAssetObj.serial}] - R$ {RefundedAmount}"
            )

            # First delete any trade items that reference this UserAsset
            UserTradeItem.query.filter_by(user_asset_id=UserAssetObj.id).delete()

            # Then delete the UserAsset itself
            db.session.delete(UserAssetObj)

        db.session.commit()

        ActualAmountOwed = min(AmountOwed, refundCapAmount[GetUserMembership(userObj)])

        # ===== CHECK FOR ROBUX OVERFLOW =====
        max_int_32 = 2_147_483_647
        if userObj.economy.robux + ActualAmountOwed > max_int_32:
            logging.warning(
                f"Skipping refund for {userObj.username}: balance would exceed 32-bit integer limit"
            )
            return
        # ====================================

        if ActualAmountOwed > 0:
            ItemListText: str = "\n".join(WipedAssets)

            IncrementTargetBalance(userObj, ActualAmountOwed, 0)
            CreateTransaction(
                Reciever=userObj,
                Sender=None,
                CurrencyAmount=ActualAmountOwed,
                CurrencyType=0,
                TransactionType=TransactionType.BuildersClubStipend,
                AssetId=None,
                CustomText="Limited item refund",
            )

            CreateSystemMessage(
                subject="Limited Item Refund",
                message=f"""Hello {userObj.username},

This is an automated message to inform you that all limited items are being refunded as we are resetting the economy. We have refunded you R$ {ActualAmountOwed} for the following items:
{ItemListText}

Item Refund Cap: R$ {refundCapAmount[GetUserMembership(userObj)]}
Total Value before Cap: R$ {AmountOwed}

Refunded Amount: R$ {ActualAmountOwed}

Please contact us on our Discord Server if you have any questions.

Sincerely,
The NEXIUM Team""",
                userid=userObj.id,
            )

            logging.info(
                f"Refunded {len(AllLimitedUserAssets)} limited items for user {userObj.username}"
            )

    AllUsersWithLimiteds: list[User] = (
        User.query.join(UserAsset, User.id == UserAsset.userid)
        .join(Asset, UserAsset.assetid == Asset.id)
        .filter(Asset.is_limited == True)
        .distinct(User.id)
        .all()
    )
    logging.info(f"Found {len(AllUsersWithLimiteds)} users with limiteds")

    for UserObj in AllUsersWithLimiteds:
        refund_user_limiteds(UserObj)


def delete_limited_assets():
    from app.models.asset import Asset
    from app.models.asset_version import AssetVersion
    from app.models.asset_thumbnail import AssetThumbnail
    from app.models.user_avatar_asset import UserAvatarAsset

    # Step 1: Get all limited asset IDs
    limited_assets = Asset.query.filter_by(is_limited=True).all()
    limited_asset_ids = [asset.id for asset in limited_assets]

    # Step 2: Delete UserAvatarAssets referencing limited assets
    UserAvatarAsset.query.filter(UserAvatarAsset.asset_id.in_(limited_asset_ids)).delete(synchronize_session=False)
    db.session.commit()

    # Step 3: Delete related asset versions
    AssetVersion.query.filter(AssetVersion.asset_id.in_(limited_asset_ids)).delete(synchronize_session=False)

    # Step 4: Delete related asset thumbnails
    AssetThumbnail.query.filter(AssetThumbnail.asset_id.in_(limited_asset_ids)).delete(synchronize_session=False)
    db.session.commit()

    # Step 5: Delete the limited assets themselves
    Asset.query.filter(Asset.id.in_(limited_asset_ids)).delete(synchronize_session=False)
    db.session.commit()

def clear_user_avatar_assets():
    from app.models.user_avatar_asset import UserAvatarAsset
    from app.models.asset import Asset
    from app.routes.thumbnailer import TakeUserThumbnail

    NeedReRender : list[int] = []

    AllUserAvatarAssets : list[UserAvatarAsset] = UserAvatarAsset.query.outerjoin(Asset, UserAvatarAsset.asset_id == Asset.id).filter(Asset.id == None).all()
    for UserAvatarAssetObj in AllUserAvatarAssets:
        if UserAvatarAssetObj.user_id not in NeedReRender:
            NeedReRender.append(UserAvatarAssetObj.user_id)
        db.session.delete(UserAvatarAssetObj)

    db.session.commit()

    for UserId in NeedReRender:
        logging.info(f"Re-rendering avatar for user {UserId}")
        TakeUserThumbnail(UserId)

def clear_bad_transactions():
    from app.models.user_transactions import UserTransaction
    from app.models.asset import Asset

    AllBadTransactions : list[UserTransaction] = UserTransaction.query.outerjoin(Asset, UserTransaction.assetId == Asset.id).filter(Asset.id == None).all()
    print(f"Found {len(AllBadTransactions)} bad transactions")
    
    while True:
        BatchTransactions : list[UserTransaction] = AllBadTransactions[:1000]
        if len(BatchTransactions) == 0:
            break
        for TransactionObj in BatchTransactions:
            db.session.delete(TransactionObj)
        db.session.commit()
        AllBadTransactions = AllBadTransactions[1000:]
        logging.info(f"Deleted 1000 bad transactions, {len(AllBadTransactions)} remaining")

def delete_asset( asset_id : int ):
    from app.models.asset import Asset
    from app.models.asset_version import AssetVersion
    from app.models.asset_thumbnail import AssetThumbnail
    
    AllAssetVersions : list[AssetVersion] = AssetVersion.query.filter_by(asset_id=asset_id).all()
    AllAssetThumbnails : list[AssetThumbnail] = AssetThumbnail.query.filter_by(asset_id=asset_id).all()

    for AssetVersionObj in AllAssetVersions:
        print(f"Deleting asset version {AssetVersionObj.id}")
        db.session.delete(AssetVersionObj)
    for AssetThumbnailObj in AllAssetThumbnails:
        print(f"Deleting asset thumbnail {AssetThumbnailObj.id}")
        db.session.delete(AssetThumbnailObj)
    
    db.session.commit()

    AssetObj : Asset = Asset.query.filter_by(id=asset_id).first()
    db.session.delete(AssetObj)

    db.session.commit()

def create_admin_user():
    """
        ! FIRST TIME SETUP ONLY !
        Creates a user with all admin permissions and a random password
        
        Note: will raise an exception if User ID 1 already exists
    """
    import datetime
    import random
    import string
    from app.models.user import User
    from app.models.admin_permissions import AdminPermissions
    from app.models.usereconomy import UserEconomy
    from app.models.user_avatar import UserAvatar
    from app.util.auth import SetPassword
    from app.pages.admin.permissionsdefinition import PermissionsDefinition

    if User.query.filter_by(id=1).first() is not None:
        raise Exception("User ID 1 already exists")
    
    NewUser : User = User(
        username = "Admin",
        password = "",
        created = datetime.datetime.utcnow(),
        lastonline = datetime.datetime.utcnow()
    )

    db.session.add(NewUser)
    db.session.commit()

    NewPassword : str = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(24))

    SetPassword(
        UserObj = NewUser,
        password = NewPassword
    )

    UserEconomyObj : UserEconomy = UserEconomy(
        userid = NewUser.id,
        robux = 0,
        tix = 0,
    )

    db.session.add(UserEconomyObj)

    UserAvatarObj : UserAvatar = UserAvatar(
        user_id = NewUser.id,
    )

    db.session.add(UserAvatarObj)
    
    for permission_name in PermissionsDefinition:
        permissionObj : AdminPermissions = AdminPermissions(
            userid = NewUser.id,
            permission = permission_name
        )
        db.session.add(permissionObj)

    db.session.commit()

    print(f"""
Successfully created Admin User
Username: Admin
Password: {NewPassword}""")
    
def convert_places_to_universes():
    from app.models.asset import Asset
    from app.models.place import Place
    from app.models.universe import Universe
    from app.models.place_datastore import PlaceDatastore
    from app.models.place_ordered_datastore import PlaceOrderedDatastore
    from app.models.legacy_data_persistence import LegacyDataPersistence
    from app.models.place_badge import PlaceBadge
    from app.models.gamepass_link import GamepassLink
    from app.models.place_developer_product import DeveloperProduct

    AllPlaces : list[Place] = Place.query.filter_by( parent_universe_id = 0 ).order_by(Place.placeid).all()
    logging.info(f"convert_places_to_universes > Found {len(AllPlaces)} places to convert")
    for PlaceObj in AllPlaces:
        PlaceObj : Place
        try:
            PlaceAssetObj : Asset = Asset.query.filter_by( id = PlaceObj.placeid ).first()
            UniverseObj : Universe = Universe(
                root_place_id = PlaceObj.placeid,
                creator_id = PlaceAssetObj.creator_id,
                creator_type = PlaceAssetObj.creator_type,
                place_rig_choice = PlaceObj.rig_choice,
                place_year = PlaceObj.placeyear,
                is_featured = PlaceObj.featured,
                minimum_account_age = PlaceObj.min_account_age,
                bc_required = PlaceObj.bc_required,
                allow_direct_join = False,
                is_public = PlaceObj.is_public,
                updated_at = PlaceAssetObj.updated_at,
                created_at = PlaceAssetObj.created_at
            )
            db.session.add(UniverseObj)
            db.session.commit()

            logging.info(f"convert_places_to_universes > Created universe {UniverseObj.id} from place {PlaceObj.placeid}")
            PlaceObj.parent_universe_id = UniverseObj.id
            db.session.commit()

            logging.info(f"convert_places_to_universes > Converting place {PlaceObj.placeid}'s items to universe {UniverseObj.id}")

            db.session.query(PlaceDatastore).filter_by( placeid = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})
            db.session.query(PlaceOrderedDatastore).filter_by( placeid = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})
            db.session.query(LegacyDataPersistence).filter_by( placeid = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})
            db.session.query(PlaceBadge).filter_by( associated_place_id = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})
            db.session.query(GamepassLink).filter_by( place_id = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})
            db.session.query(DeveloperProduct).filter_by( placeid = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})

            db.session.commit()

            logging.info(f"convert_places_to_universes > Successfully converted place {PlaceObj.placeid}'s items to universe {UniverseObj.id}")
        except Exception as e:
            logging.error(f"convert_places_to_universes > Failed to migrate place {PlaceObj.placeid}, error: {e}")

def recalculate_universe_visits():
    from app.models.place import Place
    from app.models.universe import Universe

    AllUniverses : list[Universe] = Universe.query.all()
    logging.info(f"recalculate_universe_visits > Found {len(AllUniverses)} universes to recalculate")

    for UniverseObj in AllUniverses:
        try:
            UniverseObj : Universe
            UniverseObj.visit_count = Place.query.filter_by( parent_universe_id = UniverseObj.id ).with_entities(func.sum(Place.visitcount)).scalar()
            db.session.commit()
            logging.info(f"recalculate_universe_visits > Successfully recalculated universe {UniverseObj.id}, new visit count: {UniverseObj.visit_count}")

        except Exception as e:
            logging.error(f"recalculate_universe_visits > Failed to recalculate universe {UniverseObj.id}, error: {e}")

def reverse_item_transfer():
    from app.models.limited_item_transfers import LimitedItemTransfer
    from app.models.asset import Asset
    from app.models.user import User
    from app.services.economy import IncrementTargetBalance, DecrementTargetBalance, GetAssetRap
    from app.models.asset_rap import AssetRap
    from app.models.userassets import UserAsset
    import math
    from datetime import datetime
    from sqlalchemy import and_
    from app.pages.messages.messages import CreateSystemMessage

    def reverse_transfer( ItemTransferRecordObj : LimitedItemTransfer ):
        logging.info(f"reverse_item_transfer > Reversing transfer {ItemTransferRecordObj.id}")
        RecievingUserObj : User = User.query.filter_by( id = ItemTransferRecordObj.new_owner_id ).first()
        SendingUserObj : User = User.query.filter_by( id = ItemTransferRecordObj.original_owner_id ).first()
        UserAssetObj : UserAsset = UserAsset.query.filter_by( id = ItemTransferRecordObj.user_asset_id ).first()

        if RecievingUserObj is None or SendingUserObj is None or UserAssetObj is None:
            logging.error(f"reverse_item_transfer > Unable to find user or asset for transfer {ItemTransferRecordObj.id}")
            return
        
        AssetObj : Asset = Asset.query.filter_by( id = ItemTransferRecordObj.asset_id ).first()
        AssetRapObj : AssetRap = AssetRap.query.filter_by( assetid = AssetObj.id ).first()
        CurrentRapValue : int = GetAssetRap( AssetObj )
        ReversedRapValue : int = math.floor( ( ( CurrentRapValue * 10 ) - ItemTransferRecordObj.purchased_price ) / 9 )

        RobuxGivenToOriginalOwner : int = math.floor( ItemTransferRecordObj.purchased_price * 0.7 )

        UserAssetObj.userid = ItemTransferRecordObj.original_owner_id
        UserAssetObj.updated = datetime.utcnow()
        UserAssetObj.is_for_sale = False
        AssetRapObj.rap = ReversedRapValue
        db.session.commit()

        IncrementTargetBalance( Target = RecievingUserObj, Amount = ItemTransferRecordObj.purchased_price, CurrencyType = 0 )
        DecrementTargetBalance( Target = SendingUserObj, Amount = RobuxGivenToOriginalOwner, CurrencyType = 0 )

        CreateSystemMessage( subject = "Item Transfer Reversed", message = f"""Hello {RecievingUserObj.username},
This is an automated message to inform you that a recent item transfer has been reversed. You have been refunded R$ {ItemTransferRecordObj.purchased_price} for the item {AssetObj.name} which has been taken from your inventory.
Please contact us on our Discord Server if you have any questions.

Sincerely,
The NEXIUM Team""", userid = RecievingUserObj.id )

        CreateSystemMessage( subject = "Item Transfer Reversed", message = f"""Hello {SendingUserObj.username},
This is an automated message to inform you that a recent item transfer has been reversed. R$ {RobuxGivenToOriginalOwner} has been taken from your account and your item ( {AssetObj.name} ) has been returned to your inventory.
Please contact us on our Discord Server if you have any questions.

Sincerely,
The NEXIUM Team""", userid = SendingUserObj.id )
        
        logging.info(f"reverse_item_transfer > Successfully reversed transfer {ItemTransferRecordObj.id} - R$ {ItemTransferRecordObj.purchased_price} given to {RecievingUserObj.username}, R$ {RobuxGivenToOriginalOwner} taken from {SendingUserObj.username}")
        logging.info(f"reverse_item_transfer > New RAP value for asset {AssetObj.id}: {CurrentRapValue} -> {ReversedRapValue}")

    FradulentTransfers : list[LimitedItemTransfer] = LimitedItemTransfer.query.filter(and_( LimitedItemTransfer.id > 453, LimitedItemTransfer.id < 472 )).order_by(LimitedItemTransfer.id.desc()).all()
    logging.info(f"reverse_item_transfer > Found {len(FradulentTransfers)} fradulent transfers to reverse")

    for TransferObj in FradulentTransfers:
        reverse_transfer( TransferObj )

