from typing import List
from ninja import Router
from ninja.errors import HttpError
from django.shortcuts import get_object_or_404
from django.db.models import Sum, Prefetch
from django.db.models.functions import Coalesce
from django.db import transaction

from market.models import Market, MarketOption, UserBet, UserPointChange
from market.schemas import (
    MarketListSchema,
    ResolveMarketSchema,
    UserBetCreateSchema,
    UserBetSchema,
)


router = Router(tags=["market"])


@router.get("/", response=List[MarketListSchema])
def list_markets(request):
    """List all markets (excludes draft markets)."""
    markets = Market.objects.exclude(status=Market.Status.DRAFT)
    # Prefetch options with total_bets annotation sorted by total_bets desc, then text asc
    options_queryset = MarketOption.objects.annotate(
        total_bets=Coalesce(Sum("user_bets__amount"), 0)
    ).order_by("-total_bets", "text")

    return markets.prefetch_related(Prefetch("options", queryset=options_queryset))


@router.get("/user/bets", response=List[UserBetSchema])
def list_user_bets(request):
    """List all bets placed by the current user."""
    if not request.user.is_authenticated:
        raise HttpError(401, "Authentication required")

    return (
        UserBet.objects.filter(user=request.user)
        .select_related("option__market")
        .prefetch_related("option__market__options")
    )


@router.post("/{market_uuid}/actions/close")
def close_market(request, market_uuid: str):
    """Close a market. Admin only."""
    if not request.user.is_staff:
        raise HttpError(403, "Permission denied")

    market = get_object_or_404(Market, uuid=market_uuid)
    market.status = Market.Status.CLOSED
    market.save(update_fields=["status", "updated_at"])
    return {"status": Market.Status.CLOSED}


@router.post("/{market_uuid}/actions/resolve", response=MarketListSchema)
def resolve_market(request, market_uuid: str, payload: ResolveMarketSchema):
    """Resolve a market with a winning option. Admin only."""
    if not request.user.is_staff:
        raise HttpError(403, "Permission denied")

    market = get_object_or_404(Market, uuid=market_uuid)
    winning_option = get_object_or_404(MarketOption, uuid=payload.winning_option_uuid)

    if winning_option.market_id != market.id:
        raise HttpError(400, "Option does not belong to this market")

    market.winning_option = winning_option
    market.status = Market.Status.RESOLVED
    market.save(update_fields=["winning_option", "status", "updated_at"])

    # Calculate and distribute winnings
    all_bets = list(
        UserBet.objects.filter(option__market=market).select_related("user")
    )

    # Calculate total pot
    total_pot = sum(bet.amount for bet in all_bets)
    if total_pot == 0:
        return market

    # Separate winning and losing bets
    winning_bets = [bet for bet in all_bets if bet.option_id == winning_option.id]
    losing_bets = [bet for bet in all_bets if bet.option_id != winning_option.id]

    total_winning = sum(bet.amount for bet in winning_bets)

    point_changes = []
    users_to_update = []

    with transaction.atomic():
        # Award payouts to winners with multiplier
        if total_winning > 0:
            for bet in winning_bets:
                payout = round(
                    bet.amount / total_winning * total_pot * market.multiplier
                )
                bet.user.points += payout
                users_to_update.append(bet.user)
                point_changes.append(
                    UserPointChange(
                        user=bet.user,
                        market=market,
                        amount=payout,
                        reason=UserPointChange.Reason.BET_WON,
                    )
                )

        # Record losing bets (points already deducted)
        for bet in losing_bets:
            point_changes.append(
                UserPointChange(
                    user=bet.user,
                    market=market,
                    amount=-bet.amount,
                    reason=UserPointChange.Reason.BET_LOST,
                )
            )

        # Bulk update users
        for user in users_to_update:
            user.save(update_fields=["points"])

        # Bulk create point changes
        UserPointChange.objects.bulk_create(point_changes)

    return market


@router.post("/{market_uuid}/bets", response=UserBetSchema)
def create_bet(request, market_uuid: str, payload: UserBetCreateSchema):
    """Place a bet on a market option."""
    if not request.user.is_authenticated:
        raise HttpError(401, "Authentication required")

    market = get_object_or_404(Market, uuid=market_uuid)
    option = get_object_or_404(MarketOption, uuid=payload.option_uuid)

    if option.market_id != market.id:
        raise HttpError(400, "Option does not belong to this market")

    if market.status != Market.Status.OPEN:
        raise HttpError(400, "Market is not open for betting")

    # Check if user already has a bet on a different option in this market
    existing_bet_on_market = (
        UserBet.objects.filter(user=request.user, option__market=market)
        .exclude(option=option)
        .first()
    )
    if existing_bet_on_market:
        raise HttpError(400, "You can only bet on one option per market")

    # Check if user already has a bet on this option
    existing_bet = UserBet.objects.filter(user=request.user, option=option).first()
    if existing_bet and payload.amount < existing_bet.amount:
        raise HttpError(400, "Cannot decrease bet amount. You can only increase it.")

    # Calculate delta (amount to deduct from user's points)
    delta = payload.amount - (existing_bet.amount if existing_bet else 0)

    # Check if user has enough points
    if request.user.points < delta:
        raise HttpError(400, "Insufficient points for this bet")

    user_bet, created = UserBet.objects.update_or_create(
        user=request.user,
        option=option,
        defaults={"amount": payload.amount},
    )

    # Deduct points and record the change
    request.user.points -= delta
    request.user.save(update_fields=["points"])
    UserPointChange.objects.create(
        user=request.user,
        market=market,
        amount=-delta,
        reason=UserPointChange.Reason.BET_PLACED,
    )

    return user_bet
