from __future__ import annotations

import math
import os
import random
import time
from datetime import datetime, timedelta, timezone
from typing import Optional, Dict, Any

from fastapi import FastAPI, HTTPException, Request, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, PlainTextResponse
import jwt  # PyJWT

from db import DB, init_db
from supervisor import SupervisorSystem

APP_NAME = "Poseidon Casino (Python API)"
JWT_SECRET = os.getenv("POSEIDON_JWT_SECRET", "poseidon_secret_key_2024")
JWT_ALG = "HS256"
JWT_TTL_HOURS = 2

app = FastAPI(title=APP_NAME)

# If you host PHP + API on the same domain you can lock this down to that domain.
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

db = DB(os.getenv("POSEIDON_DB_PATH", os.path.join(os.path.dirname(__file__), "data", "poseidon_casino.db")))
init_db(db)

# In-memory supervisor state (same idea as the C++ version).
player_supervisors: Dict[str, SupervisorSystem] = {}

def _now_utc() -> datetime:
    return datetime.now(timezone.utc)


def _parse_house_edge(raw_value: Any) -> float:
    """Parse house edge from admin settings.

    Supports two input styles:
    - Fractional: 0.05 == 5%
    - Percent: 5 == 5%, 20 == 20%, -100 == -100%

    Returns a clamped fraction in [-1.0, 1.0].
    """
    try:
        v = float(raw_value)
    except Exception:
        return 0.0

    # If user enters values like 20 or -100, treat as percent.
    if abs(v) > 1.0:
        v = v / 100.0

    # Full spectrum clamp: -100%..+100%
    if v > 1.0:
        v = 1.0
    if v < -1.0:
        v = -1.0
    return v


def _get_game_settings(game: str) -> Dict[str, Any]:
    # Read from shared SQLite table written by the admin panel
    s = db.get_game_settings(game)

    # House edge is stored in s["house_edge"]. We accept full spectrum:
    # -1.0 => player always wins (100% player edge)
    #  0.0 => fair (no edge)
    # +1.0 => house always wins (100% house edge)
    s["house_edge"] = _parse_house_edge(s.get("house_edge", 0.0))
    return s

def _enforce_game_rules(game: str, bet: float) -> Dict[str, Any]:
    s = _get_game_settings(game)
    if int(s.get("enabled", 1)) != 1:
        raise HTTPException(status_code=403, detail=f"{game.capitalize()} is currently disabled")
    min_bet = float(s.get("min_bet") or 0.0)
    max_bet = float(s.get("max_bet") or 0.0)
    if min_bet > 0 and bet < min_bet:
        raise HTTPException(status_code=400, detail=f"Bet must be at least {min_bet:g}")
    if max_bet > 0 and bet > max_bet:
        raise HTTPException(status_code=400, detail=f"Bet must be at most {max_bet:g}")
    return s


def _mines_multiplier(total: int, mines: int, picks: int, house_edge: float) -> float:
    """Return the (gross) multiplier for cashing out after `picks` safe reveals.

    Uses combinatorics (without replacement) to compute fair odds, then applies house edge:
      fair = 1 / P(survive `picks` picks)
      gross = fair * (1 - house_edge)

    house_edge is in [-1.0, 1.0].
    """
    if picks <= 0:
        return 1.0

    safe = total - mines
    if safe <= 0:
        return 0.0
    if picks > safe:
        return 0.0

    # P(survive k picks) = C(safe, k) / C(total, k)
    try:
        p_survive = math.comb(safe, picks) / math.comb(total, picks)
    except Exception:
        return 0.0

    if p_survive <= 0:
        return 0.0

    fair = 1.0 / p_survive
    gross = fair * (1.0 - house_edge)
    # Never allow negative/zero multipliers for UI sanity.
    if gross < 0.0:
        gross = 0.0
    return float(gross)


def _mines_make_state(user_id: int, bet: float, size: int, mines: int, mine_indices: list[int], revealed: list[int], lost: bool) -> str:
    payload = {
        "t": "mines",
        "user_id": int(user_id),
        "bet": float(bet),
        "size": int(size),
        "mines": int(mines),
        "mine_indices": list(map(int, mine_indices)),
        "revealed": list(map(int, revealed)),
        "lost": bool(lost),
        "iat": int(time.time()),
    }
    return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALG)


def _mines_read_state(token: str) -> Dict[str, Any]:
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALG])
        if payload.get("t") != "mines":
            raise HTTPException(status_code=400, detail="Invalid mines state")
        return payload
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=400, detail="Invalid mines state")

def create_jwt(user_id: int, username: str) -> str:
    exp = _now_utc() + timedelta(hours=JWT_TTL_HOURS)
    payload = {"user_id": user_id, "username": username, "exp": exp}
    return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALG)

def verify_jwt(token: str) -> Dict[str, Any]:
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALG])
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")

def get_bearer_token(req: Request) -> str:
    auth = req.headers.get("Authorization", "")
    if not auth.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="No token provided")
    return auth[7:]

def auth_user(req: Request) -> Dict[str, Any]:
    token = get_bearer_token(req)
    return verify_jwt(token)

@app.post("/api/register")
async def register(payload: Dict[str, Any]):
    username = (payload.get("username") or "").strip()
    password = (payload.get("password") or "")
    if not username or not password:
        raise HTTPException(status_code=400, detail="username and password required")

    if db.user_exists(username):
        return JSONResponse(status_code=409, content={"error": "Username already exists"})

    user_id = db.create_user(username=username, password=password)
    db.ensure_balance_row(user_id)

    token = create_jwt(user_id, username)
    return {"message": "User registered", "token": token, "username": username}

@app.post("/api/login")
async def login(payload: Dict[str, Any]):
    username = (payload.get("username") or "").strip()
    password = (payload.get("password") or "")
    if not username or not password:
        raise HTTPException(status_code=400, detail="username and password required")

    user = db.verify_user(username, password)
    if not user:
        return JSONResponse(status_code=401, content={"error": "Invalid credentials"})

    user_id = int(user["id"])
    db.ensure_balance_row(user_id)
    token = create_jwt(user_id, username)
    return {"message": "Login successful", "token": token, "username": username}

@app.get("/api/balance")
async def balance(user=Depends(auth_user)):
    user_id = int(user["user_id"])
    bal = db.get_balance(user_id)
    return {"balance": bal}


@app.get("/api/user/stats")
async def user_stats(user=Depends(auth_user)):
    user_id = int(user["user_id"])
    s = db.get_user_stats(user_id)
    return s

@app.post("/api/add-funds")
async def add_funds(payload: Dict[str, Any], user=Depends(auth_user)):
    user_id = int(user["user_id"])
    try:
        amount = float(payload.get("amount"))
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid amount")
    if amount <= 0:
        raise HTTPException(status_code=400, detail="Amount must be positive")

    db.add_balance(user_id, amount)
    db.insert_transaction(user_id=user_id, amount=amount, tx_type="deposit", game="funds")

    return {"message": "Funds added", "balance": db.get_balance(user_id)}



@app.post("/api/games/dice")
async def play_dice(payload: dict, user: dict = Depends(auth_user)):
    # NOTE: This casino stores balances in "millions".
    # Example: 2.5 means 2,500,000 GP.
    bet = float(payload.get("bet", 0))
    target = int(payload.get("target", 0))

    if bet <= 0:
        raise HTTPException(status_code=400, detail="Invalid bet")
    if target < 2 or target > 12:
        raise HTTPException(status_code=400, detail="Invalid target")

    user_id = int(user["user_id"])
    current_balance = float(db.get_balance(user_id))
    if bet > current_balance:
        raise HTTPException(status_code=400, detail="Insufficient balance")

    dice1 = random.randint(1, 6)
    dice2 = random.randint(1, 6)
    total = dice1 + dice2
    won = (total == target)

    # Ways to make each sum with two dice
    ways = {2: 1, 3: 2, 4: 3, 5: 4, 6: 5, 7: 6, 8: 5, 9: 4, 10: 3, 11: 2, 12: 1}
    w = ways.get(target, 0)

    # UI shows integer net odds (e.g. 6:1 for target 6), so we PAY that exact integer.
    # Fair net = (36 / ways) - 1; then rounded to match the UI label.
    net_mult = int(round((36 / w) - 1)) if w else 0
    if net_mult < 0:
        net_mult = 0

    payout = (bet * net_mult) if won else -bet  # NET delta applied to balance

    db.add_balance(user_id, payout)
    new_balance = float(db.get_balance(user_id))

    # Transactions
    if won:
        db.insert_transaction(user_id, bet, "dice_bet", "dice")
        db.insert_transaction(user_id, payout, "dice_win", "dice")
    else:
        db.insert_transaction(user_id, bet, "dice_bet", "dice")
        db.insert_transaction(user_id, -bet, "dice_loss", "dice")

    return {
        "dice1": dice1,
        "dice2": dice2,
        "sum": total,
        "target": target,
        "won": won,
        "bet": bet,
        "payout": payout,   # net profit (win) or -bet (loss)
        "balance": new_balance
    }


@app.post("/api/games/mines/start")
async def mines_start(payload: Dict[str, Any], user: dict = Depends(auth_user)):
    """Start a mines round.

    Payload:
      bet: float
      mines: int (number of mines)
      size: int (grid width/height, square grid)
    """
    user_id = int(user["user_id"])

    try:
        bet = float(payload.get("bet", 0))
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid bet")

    try:
        mines = int(payload.get("mines", 8))
    except Exception:
        mines = 8

    try:
        size = int(payload.get("size", 8))
    except Exception:
        size = 8

    if bet <= 0:
        raise HTTPException(status_code=400, detail="Bet must be positive")

    # Clamp grid + mines to sane limits
    if size < 5:
        size = 5
    if size > 12:
        size = 12

    total = size * size
    mines = max(1, min(mines, total - 1))

    # Enforce admin-configured rules
    settings = _enforce_game_rules("mines", bet)
    house_edge = float(settings.get("house_edge") or 0.0)

    current_balance = float(db.get_balance(user_id))
    if bet > current_balance:
        raise HTTPException(status_code=400, detail="Insufficient balance")

    # Extreme edge handling:
    #  - +100% => house always wins: still allow start, but reveal will always explode.
    #  - -100% => player always wins: mines still exist, but multipliers are boosted.

    # Generate mine positions
    all_indices = list(range(total))
    random.shuffle(all_indices)
    mine_indices = sorted(all_indices[:mines])

    # Deduct bet immediately
    db.add_balance(user_id, -bet)
    db.insert_transaction(user_id, bet, "mines_bet", "mines")

    state_token = _mines_make_state(
        user_id=user_id,
        bet=bet,
        size=size,
        mines=mines,
        mine_indices=mine_indices,
        revealed=[],
        lost=False,
    )

    return {
        "state_token": state_token,
        "size": size,
        "mines": mines,
        "revealed": [],
        "multiplier": 1.0,
        "balance": float(db.get_balance(user_id)),
    }


@app.post("/api/games/mines/reveal")
async def mines_reveal(payload: Dict[str, Any], user: dict = Depends(auth_user)):
    """Reveal a single tile."""
    user_id = int(user["user_id"])

    state_token = (payload.get("state_token") or "").strip()
    if not state_token:
        raise HTTPException(status_code=400, detail="Missing mines state")

    try:
        index = int(payload.get("index"))
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid index")

    s = _mines_read_state(state_token)
    if int(s.get("user_id")) != user_id:
        raise HTTPException(status_code=403, detail="Invalid mines state")

    size = int(s.get("size"))
    total = size * size
    mines = int(s.get("mines"))
    bet = float(s.get("bet"))
    mine_indices = set(map(int, s.get("mine_indices") or []))
    revealed = set(map(int, s.get("revealed") or []))
    lost = bool(s.get("lost"))

    if index < 0 or index >= total:
        raise HTTPException(status_code=400, detail="Index out of range")
    if lost:
        raise HTTPException(status_code=400, detail="Round already ended")
    if index in revealed:
        # Idempotent: return same state
        picks = len(revealed)
        settings = _get_game_settings("mines")
        house_edge = float(settings.get("house_edge") or 0.0)
        mult = _mines_multiplier(total, mines, picks, house_edge)
        return {
            "state_token": state_token,
            "hit_mine": False,
            "revealed": sorted(revealed),
            "multiplier": mult,
            "balance": float(db.get_balance(user_id)),
        }

    # House edge extreme: +100% always loses on first new reveal.
    settings = _get_game_settings("mines")
    house_edge = float(settings.get("house_edge") or 0.0)
    if house_edge >= 0.999:
        hit_mine = True
    else:
        hit_mine = index in mine_indices

    if hit_mine:
        lost = True
        # Log loss once
        db.insert_transaction(user_id, -bet, "mines_loss", "mines")
        new_state = _mines_make_state(user_id, bet, size, mines, list(mine_indices), sorted(revealed | {index}), True)
        return {
            "state_token": new_state,
            "hit_mine": True,
            "revealed": sorted(revealed | {index}),
            "mine_indices": sorted(mine_indices),
            "multiplier": 0.0,
            "balance": float(db.get_balance(user_id)),
        }

    # Safe reveal
    revealed.add(index)
    picks = len(revealed)
    mult = _mines_multiplier(total, mines, picks, house_edge)

    new_state = _mines_make_state(user_id, bet, size, mines, list(mine_indices), sorted(revealed), False)
    return {
        "state_token": new_state,
        "hit_mine": False,
        "revealed": sorted(revealed),
        "multiplier": mult,
        "balance": float(db.get_balance(user_id)),
    }


@app.post("/api/games/mines/cashout")
async def mines_cashout(payload: Dict[str, Any], user: dict = Depends(auth_user)):
    """Cash out a mines round."""
    user_id = int(user["user_id"])

    state_token = (payload.get("state_token") or "").strip()
    if not state_token:
        raise HTTPException(status_code=400, detail="Missing mines state")

    s = _mines_read_state(state_token)
    if int(s.get("user_id")) != user_id:
        raise HTTPException(status_code=403, detail="Invalid mines state")

    size = int(s.get("size"))
    total = size * size
    mines = int(s.get("mines"))
    bet = float(s.get("bet"))
    revealed = list(map(int, s.get("revealed") or []))
    lost = bool(s.get("lost"))

    if lost:
        raise HTTPException(status_code=400, detail="Cannot cash out after a loss")

    picks = len(revealed)
    if picks <= 0:
        raise HTTPException(status_code=400, detail="Reveal at least one tile before cashing out")

    settings = _get_game_settings("mines")
    house_edge = float(settings.get("house_edge") or 0.0)
    if house_edge >= 0.999:
        raise HTTPException(status_code=400, detail="Cannot cash out")

    mult = _mines_multiplier(total, mines, picks, house_edge)
    if mult <= 0:
        raise HTTPException(status_code=400, detail="Invalid multiplier")

    gross_return = bet * mult
    net_profit = gross_return - bet

    # Pay out gross return (bet already deducted at start)
    db.add_balance(user_id, gross_return)
    # Log net win (can be 0)
    db.insert_transaction(user_id, net_profit, "mines_win", "mines")

    # End the round
    mine_indices = list(map(int, s.get("mine_indices") or []))
    ended_state = _mines_make_state(user_id, bet, size, mines, mine_indices, revealed, True)

    return {
        "state_token": ended_state,
        "multiplier": mult,
        "payout": net_profit,
        "balance": float(db.get_balance(user_id)),
        "mine_indices": sorted(mine_indices),
        "revealed": sorted(revealed),
    }




@app.post("/api/games/blackjack")
async def play_blackjack(payload: Dict[str, Any], user: dict = Depends(auth_user)):
    # Simple 2-card Blackjack vs dealer.
    # - Player and dealer each get 2 cards from a fresh deck.
    # - Dealer "stands on 17" is informational only here (no hits in this simple version).
    # - Blackjack (A + 10-value) pays 3:2 net.
    # - Push returns 0 net.
    # - House edge (full spectrum) can flip outcomes probabilistically:
    #     +edge: convert player wins into losses with probability=edge
    #     -edge: convert player losses into wins with probability=abs(edge)

    user_id = int(user["user_id"])

    try:
        bet = float(payload.get("bet", 0))
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid bet")

    if bet <= 0:
        raise HTTPException(status_code=400, detail="Bet must be positive")

    # Enforce admin-configured rules
    settings = _enforce_game_rules("blackjack", bet)
    house_edge = float(settings.get("house_edge", 0.0))

    bal = float(db.get_balance(user_id))
    if bet > bal:
        raise HTTPException(status_code=400, detail="Insufficient balance")

    # ---------- Deck helpers ----------
    suits = ["S", "H", "D", "C"]  # Spades, Hearts, Diamonds, Clubs
    ranks = ["A","2","3","4","5","6","7","8","9","10","J","Q","K"]

    def draw_cards(n: int):
        deck = [{"rank": r, "suit": s} for s in suits for r in ranks]
        random.shuffle(deck)
        return deck[:n]

    def hand_total(cards):
        # Blackjack values: J/Q/K=10, A=1 or 11
        total = 0
        aces = 0
        for c in cards:
            r = c["rank"]
            if r in ("J","Q","K"):
                total += 10
            elif r == "A":
                aces += 1
                total += 1
            else:
                total += int(r)
        # Upgrade aces to 11 where possible (add +10 for each ace upgraded)
        for _ in range(aces):
            if total + 10 <= 21:
                total += 10
        return total

    def is_blackjack(cards):
        if len(cards) != 2:
            return False
        rs = sorted([c["rank"] for c in cards])
        ten_values = {"10","J","Q","K"}
        return ("A" in rs) and (rs[0] in ten_values or rs[1] in ten_values)

    # ---------- Deal ----------
    cards = draw_cards(4)
    player_cards = cards[0:2]
    dealer_cards = cards[2:4]

    player_total = hand_total(player_cards)
    dealer_total = hand_total(dealer_cards)

    player_bj = is_blackjack(player_cards) and player_total == 21
    dealer_bj = is_blackjack(dealer_cards) and dealer_total == 21

    # ---------- Resolve (2-card only) ----------
    if player_total > 21:
        outcome = "lose"
    elif dealer_total > 21:
        outcome = "win"
    else:
        if player_bj and not dealer_bj:
            outcome = "win"
        elif dealer_bj and not player_bj:
            outcome = "lose"
        else:
            if player_total > dealer_total:
                outcome = "win"
            elif player_total < dealer_total:
                outcome = "lose"
            else:
                outcome = "push"

    # ---------- House edge adjustment ----------
    # Full spectrum: -1..+1 (already parsed by _get_game_settings)
    if house_edge >= 1.0:
        outcome = "lose"
    elif house_edge <= -1.0:
        outcome = "win"
    else:
        if house_edge > 0 and outcome == "win":
            if random.random() < house_edge:
                outcome = "lose"
                player_bj = False
        elif house_edge < 0 and outcome == "lose":
            if random.random() < abs(house_edge):
                outcome = "win"

    # ---------- Payout (NET delta applied) ----------
    if outcome == "win":
        net = bet * (1.5 if player_bj else 1.0)
    elif outcome == "lose":
        net = -bet
    else:
        net = 0.0

    db.add_balance(user_id, net)
    new_balance = float(db.get_balance(user_id))

    # Transactions
    db.insert_transaction(user_id, bet, "blackjack_bet", "blackjack")
    if outcome == "win":
        db.insert_transaction(user_id, net, "blackjack_win", "blackjack")
    elif outcome == "lose":
        db.insert_transaction(user_id, -bet, "blackjack_loss", "blackjack")
    else:
        db.insert_transaction(user_id, 0.0, "blackjack_push", "blackjack")

    return {
        "player_cards": player_cards,
        "dealer_cards": dealer_cards,
        "player_total": player_total,
        "dealer_total": dealer_total,
        "blackjack": bool(player_bj and outcome == "win"),
        "outcome": outcome,
        "bet": bet,
        "payout": net,
        "balance": new_balance
    }


# -----------------------------
# Plinko helpers
# -----------------------------

def _plinko_multipliers(rows: int, risk: str) -> List[float]:
    """Return a list of net multipliers (profit multipliers) for each bucket."""
    rows = max(8, min(int(rows), 16))
    risk = (risk or "medium").lower().strip()

    # Base shapes for 12 rows (13 buckets), symmetric with 0.0 on edges.
    base = {
        "low":    [0.0, 0.0, 0.2, 0.3, 0.5, 0.8, 1.0, 0.8, 0.5, 0.3, 0.2, 0.0, 0.0],
        "medium": [0.0, 0.0, 0.2, 0.5, 0.8, 1.2, 2.0, 1.2, 0.8, 0.5, 0.2, 0.0, 0.0],
        "high":   [0.0, 0.0, 0.2, 0.5, 1.0, 2.0, 5.0, 2.0, 1.0, 0.5, 0.2, 0.0, 0.0],
    }
    src = base.get(risk) or base["medium"]

    n = rows + 1
    if n == len(src):
        out = src[:]
    else:
        out = []
        for i in range(n):
            t = 0.0 if n == 1 else (i * (len(src) - 1) / (n - 1))
            a = int(math.floor(t))
            b = min(len(src) - 1, a + 1)
            frac = t - a
            v = (1 - frac) * float(src[a]) + frac * float(src[b])
            out.append(v)

    return [float(f"{max(0.0, v):.2f}") for v in out]


@app.get("/api/games/plinko/config")
async def plinko_config(rows: int = 12, risk: str = "medium", user=Depends(auth_user)):
    s = _get_game_settings("plinko")
    he = float(s.get("house_edge", 0.0))
    rows = max(8, min(int(rows), 16))
    risk = (risk or "medium").lower().strip()
    multipliers = _plinko_multipliers(rows, risk)
    return {"rows": rows, "risk": risk, "house_edge": he, "multipliers": multipliers}


@app.post("/api/games/plinko")
async def play_plinko(payload: Dict[str, Any], user=Depends(auth_user)):
    user_id = int(user["user_id"])
    bet = float(payload.get("bet", 0))
    rows = int(payload.get("rows", 12))
    risk = str(payload.get("risk", "medium")).lower().strip()

    rows = max(8, min(rows, 16))
    _enforce_game_rules("plinko", bet)

    multipliers = _plinko_multipliers(rows, risk)

    s = _get_game_settings("plinko")
    he = float(s.get("house_edge", 0.0))

    # Bias step probability slightly based on house edge (subtle)
    bias = max(-0.08, min(0.08, -he * 0.08))
    p_right = 0.5 + bias

    bucket = 0
    path = []
    for _ in range(rows):
        step = 1 if random.random() < p_right else 0
        bucket += step
        path.append(step)

    bucket = max(0, min(bucket, rows))
    base_mult = float(multipliers[bucket])

    won = base_mult > 0
    payout = (bet * base_mult) if won else -bet

    # Extra edge: occasional win->loss conversion proportional to house edge
    if he > 0 and won and random.random() < he:
        won = False
        base_mult = 0.0
        payout = -bet

    db.add_balance(user_id, payout)
    new_balance = float(db.get_balance(user_id))

    db.insert_transaction(user_id, bet, "plinko_bet", "plinko")
    if payout > 0:
        db.insert_transaction(user_id, payout, "plinko_win", "plinko")
    else:
        db.insert_transaction(user_id, -bet, "plinko_loss", "plinko")

    return {
        "rows": rows,
        "risk": risk,
        "bucket": bucket,
        "path": path,
        "multiplier": base_mult,
        "multipliers": multipliers,
        "house_edge": he,
        "won": won,
        "bet": bet,
        "payout": payout,
        "balance": new_balance,
    }


@app.post("/api/games/slots")
async def play_slots(payload: Dict[str, Any], user=Depends(auth_user)):
    user_id = int(user["user_id"])
    bet = float(payload.get("bet", 0))
    _enforce_game_rules("slots", bet)

    symbols = ["Pearl", "Coral", "Anchor", "Skull", "Crown", "Gem"]
    reels = [random.choice(symbols) for _ in range(3)]

    # Simple payout: 3 match -> net multipliers, 2 match -> small
    net_mult = 0.0
    if reels[0] == reels[1] == reels[2]:
        if reels[0] == "Gem":
            net_mult = 10.0
        elif reels[0] == "Crown":
            net_mult = 6.0
        else:
            net_mult = 3.0
    elif reels[0] == reels[1] or reels[1] == reels[2] or reels[0] == reels[2]:
        net_mult = 0.5

    won = net_mult > 0
    payout = (bet * net_mult) if won else -bet

    he = float(_get_game_settings("slots").get("house_edge", 0.0))
    if he > 0 and won and random.random() < he:
        payout = -bet
        won = False
        net_mult = 0.0
    elif he < 0 and (not won) and random.random() < abs(he):
        net_mult = 0.5
        payout = bet * net_mult
        won = True

    db.add_balance(user_id, payout)
    new_balance = float(db.get_balance(user_id))

    db.insert_transaction(user_id, bet, "slots_bet", "slots")
    if payout > 0:
        db.insert_transaction(user_id, payout, "slots_win", "slots")
    else:
        db.insert_transaction(user_id, -bet, "slots_loss", "slots")

    return {"reels": reels, "multiplier": net_mult, "won": won, "bet": bet, "payout": payout, "balance": new_balance}


@app.post("/api/games/roulette")
async def play_roulette(payload: Dict[str, Any], user=Depends(auth_user)):
    user_id = int(user["user_id"])
    bet = float(payload.get("bet", 0))
    bet_type = str(payload.get("bet_type", "color")).lower().strip()  # color|number
    pick = payload.get("pick")
    _enforce_game_rules("roulette", bet)

    number = random.randint(0, 36)
    red_numbers = {1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36}
    color = "green" if number == 0 else ("red" if number in red_numbers else "black")

    won = False
    net_mult = 0.0
    if bet_type == "number":
        try:
            pick_n = int(pick)
        except Exception:
            raise HTTPException(status_code=400, detail="Invalid number pick")
        if pick_n < 0 or pick_n > 36:
            raise HTTPException(status_code=400, detail="Pick out of range")
        if number == pick_n:
            won = True
            net_mult = 35.0
    else:
        pick_c = str(pick or "").lower().strip()
        if pick_c not in {"red","black"}:
            raise HTTPException(status_code=400, detail="Invalid color pick")
        if color == pick_c:
            won = True
            net_mult = 1.0

    payout = (bet * net_mult) if won else -bet

    he = float(_get_game_settings("roulette").get("house_edge", 0.0))
    if he > 0 and won and random.random() < he:
        payout = -bet
        won = False
        net_mult = 0.0
    elif he < 0 and (not won) and random.random() < abs(he):
        won = True
        net_mult = 1.0 if bet_type != "number" else 35.0
        payout = bet * net_mult

    db.add_balance(user_id, payout)
    new_balance = float(db.get_balance(user_id))

    db.insert_transaction(user_id, bet, "roulette_bet", "roulette")
    if payout > 0:
        db.insert_transaction(user_id, payout, "roulette_win", "roulette")
    else:
        db.insert_transaction(user_id, -bet, "roulette_loss", "roulette")

    return {"number": number, "color": color, "bet_type": bet_type, "pick": pick, "multiplier": net_mult, "won": won, "bet": bet, "payout": payout, "balance": new_balance}


@app.post("/api/games/pool")
async def play_pool(payload: Dict[str, Any], user=Depends(auth_user)):
    user_id = int(user["user_id"])
    bet = float(payload.get("bet", 0))
    difficulty = str(payload.get("difficulty", "normal")).lower().strip()  # easy|normal|hard
    _enforce_game_rules("pool", bet)

    base = {"easy": 0.65, "normal": 0.52, "hard": 0.42}.get(difficulty, 0.52)
    won = random.random() < base
    net_mult = 1.0 if won else 0.0
    payout = (bet * net_mult) if won else -bet

    he = float(_get_game_settings("pool").get("house_edge", 0.0))
    if he > 0 and won and random.random() < he:
        payout = -bet
        won = False
        net_mult = 0.0
    elif he < 0 and (not won) and random.random() < abs(he):
        won = True
        net_mult = 1.0
        payout = bet * net_mult

    db.add_balance(user_id, payout)
    new_balance = float(db.get_balance(user_id))

    db.insert_transaction(user_id, bet, "pool_bet", "pool")
    if payout > 0:
        db.insert_transaction(user_id, payout, "pool_win", "pool")
    else:
        db.insert_transaction(user_id, -bet, "pool_loss", "pool")

    return {"difficulty": difficulty, "won": won, "bet": bet, "payout": payout, "balance": new_balance}





@app.post("/api/games/rocket")
async def play_rocket(payload: Dict[str, Any], user=Depends(auth_user)):
    user_id = int(user["user_id"])
    username = str(user.get("username") or payload.get("username") or "")
    action = str(payload.get("action") or "").lower().strip()
    if action not in {"start", "cashout"}:
        raise HTTPException(status_code=400, detail="Invalid action")

    supervisor = player_supervisors.setdefault(username, SupervisorSystem())

    if action == "start":
        try:
            bet = float(payload.get("bet"))
        except Exception:
            raise HTTPException(status_code=400, detail="Invalid bet")
        if bet <= 0:
            raise HTTPException(status_code=400, detail="Bet must be positive")

        # Load admin-configured settings (enabled/min/max/house edge)
        settings = _enforce_game_rules("rocket", bet)
        house_edge = float(settings.get("house_edge", 0.0))

        bal = db.get_balance(user_id)
        if bet > bal:
            raise HTTPException(status_code=400, detail="Insufficient balance")

        # Deduct bet immediately
        db.add_balance(user_id, -bet)

        # Generate crash point with a controllable full-spectrum house edge.
        # Edge interpretation:
        #   -1.0 => player always wins
        #    0.0 => fair
        #   +1.0 => house always wins
        if house_edge <= -0.999999:
            crash_point = 1e9
        elif house_edge >= 0.999999:
            crash_point = 1.01
        else:
            # Classic crash math: P(reach multiplier m) = (1 - e) / m
            # Equivalent generation: crash = (1 - e) / u  where u ~ Uniform(0,1)
            u = random.random()
            if u <= 0.0:
                u = 1e-12
            raw_crash = (1.0 - house_edge) / u

            # Keep the existing "supervisor" streak bias (retention shaping).
            if supervisor.consecutive_losses > 3:
                raw_crash *= 1.5
            elif supervisor.consecutive_wins > 2:
                raw_crash *= 0.7

            # Avoid absurd values while still allowing negative edges to be noticeable.
            crash_point = max(1.01, min(raw_crash, 1000.0))

        return {"crash_point": crash_point, "balance": db.get_balance(user_id)}

    # cashout
    try:
        bet = float(payload.get("bet"))
        multiplier = float(payload.get("multiplier"))
        crash_point = float(payload.get("crash_point") or payload.get("crashPoint"))
    except Exception:
        raise HTTPException(status_code=400, detail="Invalid cashout payload")

    if bet <= 0 or multiplier <= 0:
        raise HTTPException(status_code=400, detail="Invalid bet/multiplier")

    # Load settings again (mainly for house edge full-spectrum overrides)
    settings = _enforce_game_rules("rocket", bet)
    house_edge = float(settings.get("house_edge", 0.0))

    # Full-spectrum overrides
    if house_edge <= -0.999999:
        won = True
    elif house_edge >= 0.999999:
        won = False
    else:
        won = multiplier < crash_point

    payout = bet * multiplier if won else 0.0

    if won:
        supervisor.consecutive_wins += 1
        supervisor.consecutive_losses = 0
    else:
        supervisor.consecutive_losses += 1
        supervisor.consecutive_wins = 0

    if won:
        db.add_balance(user_id, payout)
        db.insert_transaction(user_id, payout - bet, "win", "rocket")
    # If lost, bet was already deducted on start; record loss:
    else:
        db.insert_transaction(user_id, -bet, "loss", "rocket")

    return {"won": won, "payout": payout, "balance": db.get_balance(user_id), "crashed_at": crash_point}

@app.options("/api/{path:path}")
async def preflight(path: str):
    return PlainTextResponse("OK")

#x