from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_
from typing import List
from datetime import datetime
from decimal import Decimal

from app.database import get_db
from app.models import User, SportEvent, SpreadBet, Wallet, Transaction, AdminSettings
from app.models import EventStatus, SpreadBetStatus, TeamSide, TransactionType, TransactionStatus
from app.schemas.spread_bet import SpreadBet as SpreadBetSchema, SpreadBetCreate, SpreadBetDetail
from app.routers.auth import get_current_user

router = APIRouter(prefix="/api/v1/spread-bets", tags=["spread-bets"])


@router.post("", response_model=SpreadBetSchema)
async def create_spread_bet(
    bet_data: SpreadBetCreate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Create a new spread bet on an event"""
    # Get event
    result = await db.execute(select(SportEvent).where(SportEvent.id == bet_data.event_id))
    event = result.scalar_one_or_none()

    if not event:
        raise HTTPException(status_code=404, detail="Event not found")

    if event.status != EventStatus.UPCOMING:
        raise HTTPException(status_code=400, detail="Event is not available for betting")

    if event.game_time <= datetime.utcnow():
        raise HTTPException(status_code=400, detail="Event has already started")

    # Validate spread is within range
    if bet_data.spread < event.min_spread or bet_data.spread > event.max_spread:
        raise HTTPException(
            status_code=400,
            detail=f"Spread must be between {event.min_spread} and {event.max_spread}"
        )

    # Validate stake amount
    if bet_data.stake_amount < Decimal(str(event.min_bet_amount)):
        raise HTTPException(
            status_code=400,
            detail=f"Minimum bet amount is ${event.min_bet_amount}"
        )

    if bet_data.stake_amount > Decimal(str(event.max_bet_amount)):
        raise HTTPException(
            status_code=400,
            detail=f"Maximum bet amount is ${event.max_bet_amount}"
        )

    # Check if bet already exists at this spread for this team
    existing_bet_result = await db.execute(
        select(SpreadBet).where(
            and_(
                SpreadBet.event_id == bet_data.event_id,
                SpreadBet.spread == bet_data.spread,
                SpreadBet.team == bet_data.team,
                SpreadBet.status == SpreadBetStatus.OPEN
            )
        )
    )
    existing_bet = existing_bet_result.scalar_one_or_none()

    if existing_bet:
        raise HTTPException(
            status_code=400,
            detail="A bet already exists at this spread. First come, first served!"
        )

    # Check user wallet balance
    wallet_result = await db.execute(select(Wallet).where(Wallet.user_id == current_user.id))
    wallet = wallet_result.scalar_one_or_none()

    if not wallet or wallet.balance < bet_data.stake_amount:
        raise HTTPException(status_code=400, detail="Insufficient wallet balance")

    # Get default commission from settings
    settings_result = await db.execute(select(AdminSettings).limit(1))
    settings = settings_result.scalar_one_or_none()
    house_commission = settings.default_house_commission_percent if settings else Decimal("10.00")

    # Create bet (no funds locked until matched)
    new_bet = SpreadBet(
        event_id=bet_data.event_id,
        spread=bet_data.spread,
        team=bet_data.team,
        creator_id=current_user.id,
        stake_amount=bet_data.stake_amount,
        house_commission_percent=house_commission,
        status=SpreadBetStatus.OPEN
    )

    db.add(new_bet)
    await db.commit()
    await db.refresh(new_bet)

    return new_bet


@router.post("/{bet_id}/take", response_model=SpreadBetSchema)
async def take_spread_bet(
    bet_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Take an open spread bet (auto-assigns opposite side)"""
    # Get bet with lock
    result = await db.execute(
        select(SpreadBet).where(SpreadBet.id == bet_id).with_for_update()
    )
    bet = result.scalar_one_or_none()

    if not bet:
        raise HTTPException(status_code=404, detail="Bet not found")

    if bet.status != SpreadBetStatus.OPEN:
        raise HTTPException(status_code=400, detail="Bet is no longer available")

    if bet.creator_id == current_user.id:
        raise HTTPException(status_code=400, detail="Cannot take your own bet")

    # Get event
    event_result = await db.execute(select(SportEvent).where(SportEvent.id == bet.event_id))
    event = event_result.scalar_one_or_none()

    if event.game_time <= datetime.utcnow():
        raise HTTPException(status_code=400, detail="Event has already started")

    # Check taker's wallet balance
    taker_wallet_result = await db.execute(select(Wallet).where(Wallet.user_id == current_user.id))
    taker_wallet = taker_wallet_result.scalar_one_or_none()

    if not taker_wallet or taker_wallet.balance < bet.stake_amount:
        raise HTTPException(status_code=400, detail="Insufficient wallet balance")

    # Get creator's wallet
    creator_wallet_result = await db.execute(select(Wallet).where(Wallet.user_id == bet.creator_id))
    creator_wallet = creator_wallet_result.scalar_one_or_none()

    if not creator_wallet or creator_wallet.balance < bet.stake_amount:
        raise HTTPException(status_code=400, detail="Creator no longer has sufficient funds")

    # Lock funds from both users
    async with db.begin_nested():
        # Lock creator funds
        creator_wallet.balance -= bet.stake_amount
        creator_wallet.escrow += bet.stake_amount

        # Lock taker funds
        taker_wallet.balance -= bet.stake_amount
        taker_wallet.escrow += bet.stake_amount

        # Update bet
        bet.taker_id = current_user.id
        bet.status = SpreadBetStatus.MATCHED
        bet.matched_at = datetime.utcnow()

        # Create escrow transactions
        creator_tx = Transaction(
            wallet_id=creator_wallet.id,
            user_id=bet.creator_id,
            amount=-bet.stake_amount,
            balance_after=creator_wallet.balance,
            transaction_type=TransactionType.ESCROW_LOCK,
            status=TransactionStatus.COMPLETED,
            description=f"Escrow locked for spread bet #{bet.id}"
        )
        db.add(creator_tx)

        taker_tx = Transaction(
            wallet_id=taker_wallet.id,
            user_id=current_user.id,
            amount=-bet.stake_amount,
            balance_after=taker_wallet.balance,
            transaction_type=TransactionType.ESCROW_LOCK,
            status=TransactionStatus.COMPLETED,
            description=f"Escrow locked for spread bet #{bet.id}"
        )
        db.add(taker_tx)

    await db.commit()
    await db.refresh(bet)

    return bet


@router.get("/my-active", response_model=List[SpreadBetDetail])
async def get_my_active_spread_bets(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Get current user's active spread bets"""
    result = await db.execute(
        select(SpreadBet).where(
            and_(
                ((SpreadBet.creator_id == current_user.id) | (SpreadBet.taker_id == current_user.id)),
                SpreadBet.status.in_([SpreadBetStatus.OPEN, SpreadBetStatus.MATCHED])
            )
        ).order_by(SpreadBet.created_at.desc())
    )
    bets = result.scalars().all()

    # Enrich with details
    detailed_bets = []
    for bet in bets:
        # Get creator
        creator_result = await db.execute(select(User).where(User.id == bet.creator_id))
        creator = creator_result.scalar_one()

        # Get taker if exists
        taker_username = None
        if bet.taker_id:
            taker_result = await db.execute(select(User).where(User.id == bet.taker_id))
            taker = taker_result.scalar_one()
            taker_username = taker.username

        # Get event
        event_result = await db.execute(select(SportEvent).where(SportEvent.id == bet.event_id))
        event = event_result.scalar_one()

        bet_detail = {
            **bet.__dict__,
            "creator_username": creator.username,
            "taker_username": taker_username,
            "event_home_team": event.home_team,
            "event_away_team": event.away_team,
            "event_official_spread": event.official_spread,
            "event_game_time": event.game_time
        }
        detailed_bets.append(bet_detail)

    return detailed_bets


@router.delete("/{bet_id}")
async def cancel_spread_bet(
    bet_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Cancel an open spread bet (only creator, only if not matched)"""
    result = await db.execute(select(SpreadBet).where(SpreadBet.id == bet_id))
    bet = result.scalar_one_or_none()

    if not bet:
        raise HTTPException(status_code=404, detail="Bet not found")

    if bet.creator_id != current_user.id:
        raise HTTPException(status_code=403, detail="Only the creator can cancel this bet")

    if bet.status != SpreadBetStatus.OPEN:
        raise HTTPException(status_code=400, detail="Can only cancel open bets")

    bet.status = SpreadBetStatus.CANCELLED
    await db.commit()

    return {"message": "Bet cancelled successfully"}
