from typing import Any, List from datetime import datetime, timedelta from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form from sqlalchemy.orm import Session from app import crud, models, schemas from app.api import deps from app.models.transaction import TransactionType from app.models.wallet import WalletType from app.services.file_upload import save_bot_image from app.core.email import send_bot_purchase_confirmation router = APIRouter() @router.get("/", response_model=List[schemas.Bot]) def read_bots( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_verified_user), skip: int = 0, limit: int = 100, ) -> Any: """ Retrieve available bots. """ bots = crud.bot.get_active(db, skip=skip, limit=limit) return bots @router.get("/{bot_id}", response_model=schemas.Bot) def read_bot( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_verified_user), bot_id: int, ) -> Any: """ Get a specific bot by ID. """ bot = crud.bot.get(db, id=bot_id) if not bot: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Bot not found", ) if not bot.is_active and current_user.role != "admin": raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Bot not found or not active", ) return bot @router.post("/{bot_id}/purchase", response_model=schemas.BotPurchase) def purchase_bot( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_verified_user), bot_id: int, purchase_data: schemas.BotPurchaseRequest, ) -> Any: """ Purchase a bot. """ bot = crud.bot.get(db, id=bot_id) if not bot: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Bot not found", ) if not bot.is_active: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Bot is not active", ) # Validate purchase amount if purchase_data.amount < bot.min_purchase_amount: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Minimum purchase amount is {bot.min_purchase_amount} USDT", ) if purchase_data.amount > bot.max_purchase_amount: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Maximum purchase amount is {bot.max_purchase_amount} USDT", ) # Get user's trading wallet trading_wallet = crud.wallet.get_by_user_and_type( db, user_id=current_user.id, wallet_type=WalletType.TRADING ) if not trading_wallet: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Trading wallet not found", ) # Check if user has enough balance if trading_wallet.balance < purchase_data.amount: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Insufficient funds in trading wallet", ) # Calculate expected ROI expected_roi_amount = purchase_data.amount * (bot.roi_percentage / 100) # Calculate end time start_time = datetime.utcnow() end_time = start_time + timedelta(hours=bot.duration_hours) # Create bot purchase bot_purchase_in = schemas.BotPurchaseCreate( user_id=current_user.id, bot_id=bot.id, amount=purchase_data.amount, expected_roi_amount=expected_roi_amount, start_time=start_time, end_time=end_time, status=schemas.BotPurchaseStatus.RUNNING, ) bot_purchase = crud.bot_purchase.create(db, obj_in=bot_purchase_in) # Deduct amount from trading wallet crud.wallet.update_balance(db, wallet_id=trading_wallet.id, amount=purchase_data.amount, add=False) # Create transaction record crud.transaction.create( db, obj_in=schemas.TransactionCreate( user_id=current_user.id, wallet_id=trading_wallet.id, amount=-purchase_data.amount, transaction_type=TransactionType.BOT_PURCHASE, description=f"Bot purchase - {bot.name}", bot_purchase_id=bot_purchase.id, ), ) # Send confirmation email send_bot_purchase_confirmation( email_to=current_user.email, bot_name=bot.name, amount=purchase_data.amount, expected_roi=expected_roi_amount, end_date=end_time.strftime("%Y-%m-%d %H:%M:%S UTC"), ) return bot_purchase @router.get("/purchased", response_model=List[schemas.BotPurchase]) def read_purchased_bots( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_verified_user), skip: int = 0, limit: int = 100, ) -> Any: """ Retrieve user's purchased bots. """ bot_purchases = crud.bot_purchase.get_by_user(db, user_id=current_user.id, skip=skip, limit=limit) return bot_purchases # Admin endpoints @router.post("/admin", response_model=schemas.Bot) async def create_bot( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin), name: str = Form(...), description: str = Form(None), roi_percentage: float = Form(...), duration_hours: int = Form(...), min_purchase_amount: float = Form(...), max_purchase_amount: float = Form(...), is_active: bool = Form(True), image: UploadFile = File(None), ) -> Any: """ Create a new bot (admin only). """ # Check if bot with same name already exists existing_bot = crud.bot.get_by_name(db, name=name) if existing_bot: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Bot with this name already exists", ) # Create bot bot_in = schemas.BotCreate( name=name, description=description, roi_percentage=roi_percentage, duration_hours=duration_hours, min_purchase_amount=min_purchase_amount, max_purchase_amount=max_purchase_amount, is_active=is_active, ) bot = crud.bot.create(db, obj_in=bot_in) # Save image if provided if image: image_path = save_bot_image(bot.id, image) crud.bot.update(db, db_obj=bot, obj_in={"image_path": image_path}) bot.image_path = image_path return bot @router.put("/admin/{bot_id}", response_model=schemas.Bot) async def update_bot( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin), bot_id: int, name: str = Form(None), description: str = Form(None), roi_percentage: float = Form(None), duration_hours: int = Form(None), min_purchase_amount: float = Form(None), max_purchase_amount: float = Form(None), is_active: bool = Form(None), image: UploadFile = File(None), ) -> Any: """ Update a bot (admin only). """ bot = crud.bot.get(db, id=bot_id) if not bot: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Bot not found", ) # Check if name is being changed and if it conflicts with existing bot if name and name != bot.name: existing_bot = crud.bot.get_by_name(db, name=name) if existing_bot and existing_bot.id != bot_id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Bot with this name already exists", ) # Update bot data update_data = {} if name is not None: update_data["name"] = name if description is not None: update_data["description"] = description if roi_percentage is not None: update_data["roi_percentage"] = roi_percentage if duration_hours is not None: update_data["duration_hours"] = duration_hours if min_purchase_amount is not None: update_data["min_purchase_amount"] = min_purchase_amount if max_purchase_amount is not None: update_data["max_purchase_amount"] = max_purchase_amount if is_active is not None: update_data["is_active"] = is_active # Save image if provided if image: image_path = save_bot_image(bot.id, image) update_data["image_path"] = image_path bot = crud.bot.update(db, db_obj=bot, obj_in=update_data) return bot @router.delete("/admin/{bot_id}", response_model=schemas.Bot) def delete_bot( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin), bot_id: int, ) -> Any: """ Delete a bot (admin only). """ bot = crud.bot.get(db, id=bot_id) if not bot: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Bot not found", ) # Check if bot has any active purchases active_purchases = crud.bot_purchase.get_by_bot(db, bot_id=bot_id) active_purchases = [p for p in active_purchases if p.status == "running"] if active_purchases: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot delete bot with active purchases", ) bot = crud.bot.remove(db, id=bot_id) return bot