from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from sqlalchemy import desc, func from typing import Optional from datetime import datetime from app.db.session import get_db from app.core.deps import get_current_user, get_current_buyer from app.models.user import User from app.models.carbon_project import CarbonProject from app.models.carbon_offset import CarbonOffset from app.models.transaction import Transaction from app.schemas.transaction import ( PurchaseRequest, TransactionResponse, TransactionListResponse ) from app.services.blockchain import blockchain_service import uuid router = APIRouter() @router.post("/purchase", response_model=TransactionResponse) def purchase_carbon_offsets( purchase: PurchaseRequest, current_user: User = Depends(get_current_buyer), db: Session = Depends(get_db) ): """Purchase carbon offsets from a project (Buyer only)""" # Check if user has wallet linked if not current_user.wallet_address: raise HTTPException( status_code=400, detail="Wallet must be linked to purchase carbon offsets" ) # Get project project = db.query(CarbonProject).filter( CarbonProject.id == purchase.project_id, CarbonProject.is_active == True, CarbonProject.verification_status == "verified" ).first() if not project: raise HTTPException( status_code=404, detail="Project not found or not verified" ) # Check if enough credits are available available_credits = project.total_credits_available - project.credits_sold if purchase.quantity > available_credits: raise HTTPException( status_code=400, detail=f"Not enough credits available. Available: {available_credits}" ) # Get available offset offset = db.query(CarbonOffset).filter( CarbonOffset.project_id == purchase.project_id, CarbonOffset.status == "available" ).first() if not offset: raise HTTPException( status_code=400, detail="No available carbon offsets for this project" ) # Calculate total amount total_amount = purchase.quantity * project.price_per_credit # Create transaction record transaction_hash = f"tx_{uuid.uuid4().hex[:16]}" db_transaction = Transaction( transaction_hash=transaction_hash, quantity=purchase.quantity, price_per_credit=project.price_per_credit, total_amount=total_amount, buyer_id=current_user.id, offset_id=offset.id, status="pending" ) db.add(db_transaction) # Update project credits sold project.credits_sold += purchase.quantity # Update offset quantity or status if offset.quantity <= purchase.quantity: offset.status = "sold" else: offset.quantity -= purchase.quantity # Create new offset for remaining quantity new_offset = CarbonOffset( serial_number=f"CO{project.id}-{offset.quantity}", vintage_year=offset.vintage_year, quantity=purchase.quantity, status="sold", project_id=project.id ) db.add(new_offset) try: db.commit() db.refresh(db_transaction) # In a real implementation, you would integrate with actual blockchain here # For now, we'll simulate transaction confirmation db_transaction.status = "confirmed" db_transaction.confirmed_at = datetime.utcnow() db_transaction.block_number = 12345678 # Simulated block number db_transaction.gas_used = 21000 # Simulated gas usage db.commit() db.refresh(db_transaction) return db_transaction except Exception as e: db.rollback() raise HTTPException( status_code=500, detail=f"Transaction failed: {str(e)}" ) @router.get("/my-transactions", response_model=TransactionListResponse) def get_my_transactions( page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100), status: Optional[str] = None, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get current user's transactions""" query = db.query(Transaction).filter(Transaction.buyer_id == current_user.id) if status: query = query.filter(Transaction.status == status) total = query.count() transactions = query.order_by(desc(Transaction.created_at)).offset( (page - 1) * page_size ).limit(page_size).all() return TransactionListResponse( transactions=transactions, total=total, page=page, page_size=page_size ) @router.get("/transactions/{transaction_id}", response_model=TransactionResponse) def get_transaction( transaction_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get a specific transaction""" transaction = db.query(Transaction).filter( Transaction.id == transaction_id, Transaction.buyer_id == current_user.id ).first() if not transaction: raise HTTPException(status_code=404, detail="Transaction not found") return transaction @router.get("/marketplace", response_model=dict) def get_marketplace_stats(db: Session = Depends(get_db)): """Get marketplace statistics""" # Total active projects total_projects = db.query(CarbonProject).filter( CarbonProject.is_active == True ).count() # Total verified projects verified_projects = db.query(CarbonProject).filter( CarbonProject.is_active == True, CarbonProject.verification_status == "verified" ).count() # Total credits available total_credits = db.query(CarbonProject).filter( CarbonProject.is_active == True ).with_entities( func.sum(CarbonProject.total_credits_available - CarbonProject.credits_sold) ).scalar() or 0 # Total transactions total_transactions = db.query(Transaction).filter( Transaction.status == "confirmed" ).count() # Total volume traded total_volume = db.query(Transaction).filter( Transaction.status == "confirmed" ).with_entities( func.sum(Transaction.total_amount) ).scalar() or 0 return { "total_projects": total_projects, "verified_projects": verified_projects, "total_credits_available": total_credits, "total_transactions": total_transactions, "total_volume_traded": total_volume }