from sqlalchemy.ext.asyncio import AsyncSession
from datetime import datetime
from app.models import Bet, BetStatus, TransactionType
from app.crud.bet import get_bet_by_id
from app.crud.wallet import get_user_wallet, create_transaction
from app.crud.user import update_user_stats
from app.utils.exceptions import (
    BetNotFoundError,
    BetNotAvailableError,
    CannotAcceptOwnBetError,
    InsufficientFundsError,
    NotBetParticipantError,
)


async def accept_bet(db: AsyncSession, bet_id: int, user_id: int) -> Bet:
    from sqlalchemy.orm import selectinload

    # Use transaction for atomic operations
    async with db.begin_nested():
        # Get and lock the bet
        bet = await db.get(Bet, bet_id, with_for_update=True)
        if not bet or bet.status != BetStatus.OPEN:
            raise BetNotAvailableError()

        if bet.creator_id == user_id:
            raise CannotAcceptOwnBetError()

        # Get user's wallet and verify funds
        user_wallet = await get_user_wallet(db, user_id)
        if not user_wallet or user_wallet.balance < bet.stake_amount:
            raise InsufficientFundsError()

        # Get creator's wallet and lock their funds too
        creator_wallet = await get_user_wallet(db, bet.creator_id)
        if not creator_wallet or creator_wallet.balance < bet.stake_amount:
            raise BetNotAvailableError()

        # Lock funds in escrow for both parties
        user_wallet.balance -= bet.stake_amount
        user_wallet.escrow += bet.stake_amount

        creator_wallet.balance -= bet.stake_amount
        creator_wallet.escrow += bet.stake_amount

        # Update bet
        bet.opponent_id = user_id
        bet.status = BetStatus.MATCHED

        # Create transaction records
        await create_transaction(
            db=db,
            user_id=user_id,
            wallet_id=user_wallet.id,
            transaction_type=TransactionType.ESCROW_LOCK,
            amount=-bet.stake_amount,
            balance_after=user_wallet.balance,
            reference_id=bet.id,
            description=f"Escrow for bet: {bet.title}",
        )

        await create_transaction(
            db=db,
            user_id=bet.creator_id,
            wallet_id=creator_wallet.id,
            transaction_type=TransactionType.ESCROW_LOCK,
            amount=-bet.stake_amount,
            balance_after=creator_wallet.balance,
            reference_id=bet.id,
            description=f"Escrow for bet: {bet.title}",
        )

    await db.commit()

    # Refresh and eagerly load relationships
    from sqlalchemy import select
    result = await db.execute(
        select(Bet)
        .where(Bet.id == bet_id)
        .options(selectinload(Bet.creator), selectinload(Bet.opponent))
    )
    bet = result.scalar_one()
    return bet


async def settle_bet(
    db: AsyncSession,
    bet_id: int,
    winner_id: int,
    settler_id: int
) -> Bet:
    async with db.begin_nested():
        bet = await get_bet_by_id(db, bet_id)
        if not bet:
            raise BetNotFoundError()

        # Verify settler is a participant
        if settler_id not in [bet.creator_id, bet.opponent_id]:
            raise NotBetParticipantError()

        # Verify winner is a participant
        if winner_id not in [bet.creator_id, bet.opponent_id]:
            raise ValueError("Invalid winner")

        # Determine loser
        loser_id = bet.opponent_id if winner_id == bet.creator_id else bet.creator_id

        # Get wallets
        winner_wallet = await get_user_wallet(db, winner_id)
        loser_wallet = await get_user_wallet(db, loser_id)

        if not winner_wallet or not loser_wallet:
            raise ValueError("Wallet not found")

        # Calculate payout (winner gets both stakes)
        total_payout = bet.stake_amount * 2

        # Release escrow and distribute funds
        winner_wallet.escrow -= bet.stake_amount
        winner_wallet.balance += total_payout

        loser_wallet.escrow -= bet.stake_amount

        # Update bet
        bet.winner_id = winner_id
        bet.status = BetStatus.COMPLETED
        bet.settled_at = datetime.utcnow()
        bet.settled_by = "participant"

        # Create transaction records
        await create_transaction(
            db=db,
            user_id=winner_id,
            wallet_id=winner_wallet.id,
            transaction_type=TransactionType.BET_WON,
            amount=total_payout,
            balance_after=winner_wallet.balance,
            reference_id=bet.id,
            description=f"Won bet: {bet.title}",
        )

        await create_transaction(
            db=db,
            user_id=loser_id,
            wallet_id=loser_wallet.id,
            transaction_type=TransactionType.BET_LOST,
            amount=-bet.stake_amount,
            balance_after=loser_wallet.balance,
            reference_id=bet.id,
            description=f"Lost bet: {bet.title}",
        )

        # Update user stats
        await update_user_stats(db, winner_id, won=True)
        await update_user_stats(db, loser_id, won=False)

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


async def cancel_bet(db: AsyncSession, bet_id: int, user_id: int) -> Bet:
    async with db.begin_nested():
        bet = await get_bet_by_id(db, bet_id)
        if not bet:
            raise BetNotFoundError()

        if bet.creator_id != user_id:
            raise NotBetParticipantError()

        if bet.status != BetStatus.OPEN:
            raise BetNotAvailableError()

        bet.status = BetStatus.CANCELLED

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