import random
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from django.shortcuts import get_object_or_404
from .models import LudoGame, LudoPlayer

COLORS = ["red", "green", "yellow", "blue"]

# Ludo board constants
# Main track positions 0-51 (52 squares around the board)
# Each color starts at a different position on the main track
COLOR_START = {"red": 0, "green": 13, "yellow": 26, "blue": 39}
COLOR_HOME_ENTRY = {"red": 51, "green": 12, "yellow": 25, "blue": 38}
# Home column positions: 100-105 (6 squares to home)
# Position 200 = reached home
SAFE_SQUARES = {0, 8, 13, 21, 26, 34, 39, 47}  # star squares


def initial_tokens():
    """All tokens start at base (-1)."""
    return {"red": [-1,-1,-1,-1], "green": [-1,-1,-1,-1],
            "yellow": [-1,-1,-1,-1], "blue": [-1,-1,-1,-1]}


def serialize_game(game, user_id=None):
    players = []
    for ps in game.player_states.select_related("user").all():
        players.append({
            "user_id": ps.user.id,
            "username": ps.user.username,
            "color": ps.color,
            "is_bot": ps.is_bot,
            "finished": ps.finished,
            "profile_image": ps.user.profile_image.url if ps.user.profile_image else None,
        })
    current_user_id = game.turn_order[game.current_idx] if game.turn_order else None
    return {
        "id": game.id,
        "status": game.status,
        "stake": game.stake,
        "max_players": game.max_players,
        "players": players,
        "turn_order": game.turn_order,
        "current_player_id": current_user_id,
        "dice_value": game.dice_value,
        "dice_rolled": game.dice_rolled,
        "tokens": game.tokens,
        "color_map": game.color_map,
        "winner_id": game.winner_id,
        "creator_id": game.creator_id,
    }


def can_move_token(color, token_idx, dice, tokens):
    """Check if a token can move given dice value."""
    pos = tokens[color][token_idx]
    if pos == -1:  # in base
        return dice == 6
    if pos == 200:  # already home
        return False
    if pos >= 100:  # in home column
        steps_left = 106 - pos
        return dice <= steps_left
    return True


def move_token(color, token_idx, dice, tokens, all_colors):
    """Move token and handle captures. Returns updated tokens."""
    tokens = {c: list(t) for c, t in tokens.items()}
    pos = tokens[color][token_idx]
    
    if pos == -1:  # leaving base
        tokens[color][token_idx] = COLOR_START[color]
        return tokens, False
    
    start = COLOR_START[color]
    home_entry = COLOR_HOME_ENTRY[color]
    
    if pos >= 100:  # in home column
        tokens[color][token_idx] = min(pos + dice, 106)
        if tokens[color][token_idx] >= 106:
            tokens[color][token_idx] = 200  # home!
        return tokens, False
    
    # Calculate new position on main track
    # Distance from start
    dist = (pos - start) % 52
    new_dist = dist + dice
    
    # Check if entering home column
    if new_dist > 51:  # passed home entry
        home_steps = new_dist - 51
        tokens[color][token_idx] = 100 + home_steps
        if tokens[color][token_idx] >= 106:
            tokens[color][token_idx] = 200
        return tokens, False
    
    new_pos = (start + new_dist) % 52
    tokens[color][token_idx] = new_pos
    
    # Check capture
    captured = False
    if new_pos not in SAFE_SQUARES:
        for other_color in all_colors:
            if other_color == color:
                continue
            for i, opos in enumerate(tokens[other_color]):
                if opos == new_pos:
                    tokens[other_color][i] = -1  # send back to base
                    captured = True
    
    return tokens, captured


def get_movable_tokens(color, dice, tokens):
    """Return list of token indices that can move."""
    return [i for i in range(4) if can_move_token(color, i, dice, tokens)]


def bot_move(game):
    """Bot AI makes a move."""
    color_map = game.color_map
    current_pid = str(game.turn_order[game.current_idx])
    bot_color = None
    for uid, c in color_map.items():
        if str(uid) == current_pid:
            bot_color = c
            break
    if not bot_color:
        return
    
    dice = game.dice_value
    tokens = game.tokens
    movable = get_movable_tokens(bot_color, dice, tokens)
    
    if not movable:
        # No moves — advance turn
        advance_turn(game)
        return
    
    # Strategy: capture > move furthest > release from base
    all_colors = list(color_map.values())
    best = None
    
    # Check captures
    for i in movable:
        pos = tokens[bot_color][i]
        if pos == -1:
            continue
        start = COLOR_START[bot_color]
        dist = (pos - start) % 52
        new_dist = dist + dice
        if new_dist <= 51:
            new_pos = (start + new_dist) % 52
            for oc in all_colors:
                if oc == bot_color:
                    continue
                if new_pos in tokens[oc] and new_pos not in SAFE_SQUARES:
                    best = i
                    break
        if best is not None:
            break
    
    if best is None:
        # Move furthest token
        max_dist = -2
        for i in movable:
            pos = tokens[bot_color][i]
            if pos == -1:
                continue
            start = COLOR_START[bot_color]
            dist = (pos - start) % 52
            if dist > max_dist:
                max_dist = dist
                best = i
    
    if best is None:
        best = movable[0]
    
    # Apply move
    new_tokens, _ = move_token(bot_color, best, dice, tokens, all_colors)
    game.tokens = new_tokens
    game.dice_rolled = False
    game.dice_value = 0
    
    # Check win
    if all(p == 200 for p in new_tokens[bot_color]):
        game.status = "finished"
        # Find bot user
        for uid, c in color_map.items():
            if c == bot_color:
                from account.models import User
                try:
                    game.winner = User.objects.get(id=int(uid))
                except: pass
                break
    else:
        if dice != 6:
            advance_turn(game)
    
    game.save()


def advance_turn(game):
    """Move to next player's turn."""
    n = len(game.turn_order)
    game.current_idx = (game.current_idx + 1) % n
    game.dice_rolled = False
    game.dice_value = 0
    game.consecutive_sixes = 0


class LudoListView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        games = LudoGame.objects.filter(
            player_states__user=request.user
        ).prefetch_related("player_states__user").distinct().order_by("-created_at")[:20]
        return Response([serialize_game(g, request.user.id) for g in games])

    def post(self, request):
        stake = int(request.data.get("stake", 0))
        max_players = int(request.data.get("max_players", 4))
        vs_bot = request.data.get("vs_bot", False)
        
        if stake > 0:
            from wallet.models import Wallet
            w, _ = Wallet.objects.get_or_create(user=request.user)
            if w.balance < stake:
                return Response({"error": "Insufficient balance."}, status=400)
            w.balance -= stake
            w.save(update_fields=["balance"])
        
        color = COLORS[0]
        game = LudoGame.objects.create(
            creator=request.user, stake=stake, max_players=max_players,
            tokens=initial_tokens(),
            color_map={str(request.user.id): color},
        )
        LudoPlayer.objects.create(game=game, user=request.user, color=color)
        game.players.add(request.user)
        
        if vs_bot:
            # Add bot players
            from account.models import User
            try:
                bot = User.objects.get(username="MauaBot")
                bot_colors = COLORS[1:max_players]
                for bc in bot_colors:
                    LudoPlayer.objects.create(game=game, user=bot, color=bc, is_bot=True)
                    game.color_map[str(bot.id)] = bc
                game.save()
                # Auto-start
                _start_game(game)
            except User.DoesNotExist:
                pass
        
        return Response(serialize_game(game, request.user.id), status=201)


def _start_game(game):
    import random as _r
    players = list(game.player_states.select_related("user").all())
    _r.shuffle(players)
    game.turn_order = [ps.user.id for ps in players]
    game.current_idx = 0
    game.status = "playing"
    game.dice_rolled = False
    game.save()


class LudoOpenView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        games = LudoGame.objects.filter(
            status="waiting"
        ).exclude(player_states__user=request.user).prefetch_related("player_states__user")[:20]
        return Response([serialize_game(g) for g in games])


class LudoDetailView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, game_id):
        game = get_object_or_404(LudoGame, id=game_id)
        return Response(serialize_game(game, request.user.id))


class LudoJoinView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request, game_id):
        game = get_object_or_404(LudoGame, id=game_id, status="waiting")
        if game.player_states.filter(user=request.user).exists():
            return Response({"error": "Already joined."}, status=400)
        if game.player_states.count() >= game.max_players:
            return Response({"error": "Game full."}, status=400)
        
        if game.stake > 0:
            from wallet.models import Wallet
            w, _ = Wallet.objects.get_or_create(user=request.user)
            if w.balance < game.stake:
                return Response({"error": "Insufficient balance."}, status=400)
            w.balance -= game.stake
            w.save(update_fields=["balance"])
        
        used = set(game.color_map.values())
        color = next(c for c in COLORS if c not in used)
        LudoPlayer.objects.create(game=game, user=request.user, color=color)
        game.color_map[str(request.user.id)] = color
        game.save()
        
        return Response(serialize_game(game, request.user.id))


class LudoStartView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request, game_id):
        game = get_object_or_404(LudoGame, id=game_id, status="waiting", creator=request.user)
        if game.player_states.count() < 2:
            return Response({"error": "Need at least 2 players."}, status=400)
        _start_game(game)
        return Response(serialize_game(game, request.user.id))


class LudoMoveView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request, game_id):
        game = get_object_or_404(LudoGame, id=game_id, status="playing")
        current_pid = game.turn_order[game.current_idx]
        if str(request.user.id) != str(current_pid):
            return Response({"error": "Not your turn."}, status=400)
        
        action = request.data.get("action")
        color = game.color_map.get(str(request.user.id))
        all_colors = list(game.color_map.values())
        
        if action == "roll":
            if game.dice_rolled:
                return Response({"error": "Already rolled."}, status=400)
            dice = random.randint(1, 6)
            game.dice_value = dice
            game.dice_rolled = True
            
            # Check if any moves possible
            movable = get_movable_tokens(color, dice, game.tokens)
            if not movable:
                # No moves — advance turn (unless 6)
                if dice == 6:
                    game.consecutive_sixes = game.consecutive_sixes + 1
                    if game.consecutive_sixes >= 3:
                        advance_turn(game)
                else:
                    advance_turn(game)
            else:
                if dice == 6:
                    game.consecutive_sixes = game.consecutive_sixes + 1
                    if game.consecutive_sixes >= 3:
                        game.tokens[color] = [-1,-1,-1,-1]  # penalty
                        advance_turn(game)
            
            game.save()
            
            # Trigger bot if needed
            _trigger_bot_if_needed(game)
            game.refresh_from_db()
            return Response(serialize_game(game, request.user.id))
        
        if action == "move":
            if not game.dice_rolled:
                return Response({"error": "Roll dice first."}, status=400)
            token_idx = int(request.data.get("token", 0))
            dice = game.dice_value
            
            if not can_move_token(color, token_idx, dice, game.tokens):
                return Response({"error": "Invalid move."}, status=400)
            
            new_tokens, captured = move_token(color, token_idx, dice, game.tokens, all_colors)
            game.tokens = new_tokens
            
            # Check win
            if all(p == 200 for p in new_tokens[color]):
                ps = LudoPlayer.objects.get(game=game, user=request.user)
                ps.finished = True
                ps.save()
                # Check if all finished
                unfinished = game.player_states.filter(finished=False).count()
                if unfinished <= 1:
                    game.status = "finished"
                    game.winner = request.user
                    if game.stake > 0:
                        pot = game.stake * game.player_states.count()
                        from wallet.models import Wallet, Transaction
                        w, _ = Wallet.objects.get_or_create(user=request.user)
                        w.balance += pot
                        w.save(update_fields=["balance"])
                        Transaction.objects.create(wallet=w, amount=pot, tx_type="prediction_win", description=f"Ludo win #{game.id}")
            
            if game.status != "finished":
                if dice == 6 and not captured:
                    game.dice_rolled = False  # extra turn
                    game.dice_value = 0
                else:
                    advance_turn(game)
            
            game.save()
            _trigger_bot_if_needed(game)
            game.refresh_from_db()
            return Response(serialize_game(game, request.user.id))
        
        return Response({"error": "Invalid action."}, status=400)


def _trigger_bot_if_needed(game):
    if game.status != "playing":
        return
    try:
        from account.models import User
        bot = User.objects.get(username="MauaBot")
        current_pid = game.turn_order[game.current_idx]
        ps = game.player_states.filter(user__id=current_pid).first()
        if ps and ps.is_bot:
            # Roll dice for bot
            dice = random.randint(1, 6)
            game.dice_value = dice
            game.dice_rolled = True
            game.save()
            bot_move(game)
    except Exception as e:
        print(f"Bot error: {e}")


class LudoBotMoveView(APIView):
    """Frontend polls this when it's bot's turn."""
    permission_classes = [IsAuthenticated]

    def post(self, request, game_id):
        game = get_object_or_404(LudoGame, id=game_id, status="playing")
        current_pid = game.turn_order[game.current_idx]
        ps = game.player_states.filter(user__id=current_pid).first()
        if not ps or not ps.is_bot:
            return Response({"status": "not bot turn"})
        # Roll dice for bot
        import random
        dice = random.randint(1, 6)
        game.dice_value = dice
        game.dice_rolled = True
        game.save()
        bot_move(game)
        game.refresh_from_db()
        return Response(serialize_game(game, request.user.id))


class LudoDeleteView(APIView):
    permission_classes = [IsAuthenticated]

    def delete(self, request, game_id):
        game = LudoGame.objects.filter(
            id=game_id
        ).filter(
            player_states__user=request.user
        ).first()
        if not game:
            return Response({"error": "Not found."}, status=404)
        if game.status == "playing" and game.creator != request.user:
            return Response({"error": "Cannot delete active game."}, status=400)
        game.delete()
        return Response({"status": "deleted"})
