from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_
from sqlalchemy.orm import selectinload
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
from app.routers.websocket import broadcast_to_event

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)
):
    # Get the event
    event_result = await db.execute(
        select(SportEvent).where(SportEvent.id == bet_data.event_id)
    )
    event = 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 open for betting")

    # 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 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}"
        )

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

    # Create the bet
    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=commission,
        status=SpreadBetStatus.OPEN
    )
    db.add(bet)
    await db.commit()
    await db.refresh(bet)

    # Broadcast bet created event to all subscribers of this event
    await broadcast_to_event(
        bet.event_id,
        "bet_created",
        {
            "bet_id": bet.id,
            "spread": float(bet.spread),
            "team": bet.team.value,
            "stake_amount": float(bet.stake_amount),
            "creator_username": current_user.username,
        }
    )

    return 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)
):
    # Get the bet
    bet_result = await db.execute(
        select(SpreadBet)
        .options(selectinload(SpreadBet.event))
        .where(SpreadBet.id == bet_id)
    )
    bet = 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 not open")

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

    if bet.event.status != EventStatus.UPCOMING:
        raise HTTPException(status_code=400, detail="Event is no longer open for betting")

    # Get wallets
    creator_wallet_result = await db.execute(
        select(Wallet).where(Wallet.user_id == bet.creator_id)
    )
    creator_wallet = creator_wallet_result.scalar_one_or_none()

    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:
        raise HTTPException(status_code=400, detail="Wallet not found")

    # Check taker has sufficient balance
    if taker_wallet.balance < bet.stake_amount:
        raise HTTPException(
            status_code=400,
            detail=f"Insufficient balance. Need ${bet.stake_amount}"
        )

    # Check creator still has sufficient balance
    if creator_wallet.balance < bet.stake_amount:
        raise HTTPException(
            status_code=400,
            detail="Creator no longer has sufficient balance"
        )

    # Lock funds for both parties
    creator_wallet.balance -= bet.stake_amount
    creator_wallet.escrow += bet.stake_amount
    taker_wallet.balance -= bet.stake_amount
    taker_wallet.escrow += bet.stake_amount

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

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

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

    # Broadcast bet taken event to all subscribers of this event
    await broadcast_to_event(
        bet.event_id,
        "bet_taken",
        {
            "bet_id": bet.id,
            "spread": float(bet.spread),
            "team": bet.team.value,
            "stake_amount": float(bet.stake_amount),
            "taker_username": current_user.username,
        }
    )

    return bet


@router.get("/my-active", response_model=List[SpreadBetDetail])
async def get_my_active_bets(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    result = await db.execute(
        select(SpreadBet)
        .options(
            selectinload(SpreadBet.event),
            selectinload(SpreadBet.creator),
            selectinload(SpreadBet.taker)
        )
        .where(
            and_(
                SpreadBet.status.in_([SpreadBetStatus.OPEN, SpreadBetStatus.MATCHED]),
                (SpreadBet.creator_id == current_user.id) | (SpreadBet.taker_id == current_user.id)
            )
        )
        .order_by(SpreadBet.created_at.desc())
    )
    bets = result.scalars().all()

    return [
        SpreadBetDetail(
            id=bet.id,
            event_id=bet.event_id,
            spread=bet.spread,
            team=bet.team,
            creator_id=bet.creator_id,
            taker_id=bet.taker_id,
            stake_amount=bet.stake_amount,
            house_commission_percent=bet.house_commission_percent,
            status=bet.status,
            payout_amount=bet.payout_amount,
            winner_id=bet.winner_id,
            created_at=bet.created_at,
            matched_at=bet.matched_at,
            completed_at=bet.completed_at,
            creator_username=bet.creator.username,
            taker_username=bet.taker.username if bet.taker else None,
            event_home_team=bet.event.home_team,
            event_away_team=bet.event.away_team,
            event_official_spread=bet.event.official_spread,
            event_game_time=bet.event.game_time
        )
        for bet in bets
    ]


@router.get("/my-history", response_model=List[SpreadBetDetail])
async def get_my_bet_history(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Get user's completed, cancelled, and disputed bets (history)."""
    result = await db.execute(
        select(SpreadBet)
        .options(
            selectinload(SpreadBet.event),
            selectinload(SpreadBet.creator),
            selectinload(SpreadBet.taker)
        )
        .where(
            and_(
                SpreadBet.status.in_([SpreadBetStatus.COMPLETED, SpreadBetStatus.CANCELLED, SpreadBetStatus.DISPUTED]),
                (SpreadBet.creator_id == current_user.id) | (SpreadBet.taker_id == current_user.id)
            )
        )
        .order_by(SpreadBet.created_at.desc())
        .limit(50)
    )
    bets = result.scalars().all()

    return [
        SpreadBetDetail(
            id=bet.id,
            event_id=bet.event_id,
            spread=bet.spread,
            team=bet.team,
            creator_id=bet.creator_id,
            taker_id=bet.taker_id,
            stake_amount=bet.stake_amount,
            house_commission_percent=bet.house_commission_percent,
            status=bet.status,
            payout_amount=bet.payout_amount,
            winner_id=bet.winner_id,
            created_at=bet.created_at,
            matched_at=bet.matched_at,
            completed_at=bet.completed_at,
            creator_username=bet.creator.username,
            taker_username=bet.taker.username if bet.taker else None,
            event_home_team=bet.event.home_team,
            event_away_team=bet.event.away_team,
            event_official_spread=bet.event.official_spread,
            event_game_time=bet.event.game_time
        )
        for bet in 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)
):
    bet_result = await db.execute(
        select(SpreadBet).where(SpreadBet.id == bet_id)
    )
    bet = 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")

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

    # Broadcast bet cancelled event to all subscribers of this event
    await broadcast_to_event(
        event_id,
        "bet_cancelled",
        {
            "bet_id": bet_id,
            "spread": float(bet.spread),
            "team": bet.team.value,
        }
    )

    return {"message": "Bet cancelled"}
