import asyncio
from fastapi import FastAPI, Request, Depends, HTTPException, Form, Response, WebSocket, WebSocketDisconnect
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from sqlalchemy import desc
from typing import Optional, List
import json
import random
import math
from datetime import datetime, timedelta
from pathlib import Path
import json
from pathlib import Path

from database import get_db, SessionLocal, User, InventoryItem, CaseOpening, Contract, Upgrade, UpgradeRPU
from websocket_manager import manager, crash_game
from achievements import (
    seed_achievements, check_achievement, check_open_cases_achievements, check_contracts_achievements,
    check_upgrade_achievements, check_rare_item_achievement, check_knife_achievement,
    check_spent_achievements, check_balance_achievement, check_slot_match_achievement,
    check_slot_plays_achievements, check_inventory_achievement, get_user_achievements
)
from auth import (
    create_user, authenticate_user, create_access_token,
    get_current_user, get_current_user_optional, get_password_hash,
    ACCESS_TOKEN_EXPIRE_MINUTES, is_item_craftable
)

from admin import admin_router

import sys
sys.path.append(str(Path(__file__).parent.parent))
from backend import (
    ALL_ITEMS, get_item, COLLECTIONS_INDEX,
    simulate_trade_up, calculate_average_float, RARITY_ORDER,
    get_rarity_level, is_knife, is_gloves, extract_base_name,
    get_possible_outcomes
)

from upgrade import (
    get_upgrade_rpu, calculate_upgrade_probability, 
    get_adjusted_upgrade_probability, spin_upgrade_wheel,
    update_upgrade_stats
)

from rpu import get_adjusted_weights, update_rpu_stats, RARITY_TO_FIELD

from auth import get_password_hash

# Создаем отдельное приложение для веб-интерфейса
app = FastAPI(title="CS2 Trade-Up Simulator Web")

app.include_router(admin_router)

# Статические файлы и шаблоны
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

def money_filter(value):
    try:
        return "{:,.0f}".format(float(value)).replace(",", " ")
    except (ValueError, TypeError):
        return "0"

templates.env.filters["money"] = money_filter

# Создаем отдельный роутер для API оригинального симулятора
from backend import app as api_app

# Монтируем оригинальное API на /api/simulator чтобы избежать конфликтов
app.mount("/api/simulator", api_app)

# ─── Вспомогательные функции ───────────────────────────────────────────────

def reset_pidor_password():
    db = next(get_db())
    # Ищем пользователя с id=2 И username="pidor"
    user = db.query(User).filter(User.id == 2, User.username == "pidor").first()
    
    if user:
        # Хешируем новый пароль
        new_hashed_password = get_password_hash("adamkadirov123")
        user.hashed_password = new_hashed_password
        db.commit()
        print(f"Пароль пользователя {user.username} (id={user.id}) сброшен на 'adamkadirov123'")
    else:
        print("Пользователь с id=2 и username='pidor' не найден")

def make_first_user_admin():
    db = next(get_db())
    user = db.query(User).first()
    if user:
        user.is_admin = True
        db.commit()
        print(f"Пользователь {user.username} стал администратором")


# Загрузка конфигурации кейсов
MAINPAGE_FILE = Path("mainpage.json")
CASES_FILE = Path("cases.json")

CASE_RARITY_WEIGHTS = {
    "Consumer Grade": 1000,
    "Industrial Grade": 500,
    "Mil-Spec": 250,
    "Mil-Spec Grade": 250,
    "Restricted": 125,
    "Classified": 25,
    "Covert": 5,
    "Rare Special Item": 1,
    "Extraordinary": 1
}

def load_mainpage_config():
    """Загружает конфигурацию главной страницы кейсов"""
    if MAINPAGE_FILE.exists():
        try:
            with open(MAINPAGE_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            print(f"Ошибка загрузки mainpage.json: {e}")
    return [
        {
            "tab": "Кейсы",
            "cases": []
        }
    ]

def load_cases_config():
    """Загружает конфигурацию кейсов"""
    if CASES_FILE.exists():
        try:
            with open(CASES_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            print(f"Ошибка загрузки cases.json: {e}")
    return []

MAINPAGE_CONFIG = load_mainpage_config()
CASES_CONFIG = {c['name']: c for c in load_cases_config()}

print(f"Загружено секций: {len(MAINPAGE_CONFIG)}")
print(f"Загружено кейсов: {len(CASES_CONFIG)}")

from database import ActivityFeed

def record_activity_sync(db: Session, user_id: int, username: str, activity_type: str, message: str, data: dict = None) -> ActivityFeed:
    activity = ActivityFeed(
        user_id=user_id,
        username=username,
        activity_type=activity_type,
        message=message,
        data_json=json.dumps(data or {}, ensure_ascii=False)
    )
    db.add(activity)
    try:
        db.commit()
        db.refresh(activity)
    except Exception:
        db.rollback()
    return activity

def record_activity_own_session(user_id: int, username: str, activity_type: str, message: str, data: dict = None):
    """Сохраняет активность в отдельной сессии — возвращает dict для broadcast."""
    try:
        own_db = SessionLocal()
        activity = ActivityFeed(
            user_id=user_id,
            username=username,
            activity_type=activity_type,
            message=message,
            data_json=json.dumps(data or {}, ensure_ascii=False)
        )
        own_db.add(activity)
        own_db.commit()
        own_db.refresh(activity)
        result = {
            "id": activity.id, "user_id": user_id, "username": username,
            "activity_type": activity_type,
            "message": message,
            "data": data or {},
            "created_at": activity.created_at.isoformat()
        }
        own_db.close()
        return result
    except Exception as e:
        print(f"record_activity_own_session error: {e}")
        return {
            "id": 0, "user_id": user_id, "username": username,
            "activity_type": activity_type,
            "message": message,
            "data": data or {},
            "created_at": datetime.utcnow().isoformat()
        }

# Сидируем достижения при старте
db_seed = next(get_db())
seed_achievements(db_seed)
db_seed.close()

async def broadcast_online_count():
    """Рассылает всем количество игроков онлайн"""
    try:
        await manager.broadcast({"type": "online_count", "online": manager.online_count})
    except:
        pass

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    """WebSocket для реал-тайм обновлений"""
    token = websocket.cookies.get("access_token", "")
    if token.startswith("Bearer "):
        token = token[7:]
    
    from auth import decode_token, get_current_user_from_token
    payload = decode_token(token)
    user_id = 0
    if payload:
        username = payload.get("sub")
        db = next(get_db())
        user = db.query(User).filter(User.username == username).first()
        if user:
            user_id = user.id
        db.close()
    
    await manager.connect(websocket, user_id)
    await broadcast_online_count()
    try:
        while True:
            data = await websocket.receive_text()
            try:
                msg = json.loads(data)
                if msg.get("type") == "ping":
                    await websocket.send_json({"type": "pong"})
            except:
                pass
    except WebSocketDisconnect:
        await manager.disconnect(websocket, user_id)
        await broadcast_online_count()

def generate_item_float(rarity: str) -> float:
    """Генерирует float для предмета в зависимости от редкости"""
    weights = CASE_RARITY_WEIGHTS
    
    if rarity in ["Covert", "Rare Special Item", "Extraordinary"]:
        # Для редких предметов float ближе к минимальному
        return round(random.uniform(0.01, 0.15), 4)
    elif rarity in ["Classified"]:
        return round(random.uniform(0.01, 0.25), 4)
    elif rarity in ["Restricted"]:
        return round(random.uniform(0.01, 0.35), 4)
    else:
        return round(random.uniform(0.01, 0.45), 4)

def open_case(case_name: str) -> dict:
    """Открывает кейс и возвращает предмет"""
    case_items = []
    
    # Ищем предметы в кейсе
    search_name = f"Case: {case_name}"
    if search_name in COLLECTIONS_INDEX:
        for idx in COLLECTIONS_INDEX[search_name]:
            item = get_item(idx)
            if item:
                case_items.append((idx, item))
    
    if not case_items:
        return None
    
    # Взвешенный выбор по редкости
    items_with_weights = []
    for idx, item in case_items:
        rarity = item.get("rarity", "Unknown")
        weight = CASE_RARITY_WEIGHTS.get(rarity, 100)
        items_with_weights.append((idx, item, weight))
    
    total_weight = sum(w for _, _, w in items_with_weights)
    rand_val = random.uniform(0, total_weight)
    
    cumsum = 0
    chosen_item = None
    chosen_idx = None
    
    for idx, item, weight in items_with_weights:
        cumsum += weight
        if rand_val <= cumsum:
            chosen_item = item
            chosen_idx = idx
            break
    
    if not chosen_item:
        chosen_idx, chosen_item = items_with_weights[-1][:2]
    
    return {
        "id": chosen_idx,
        "item": chosen_item,
        "float": generate_item_float(chosen_item.get("rarity", "Unknown"))
    }

# ─── Веб-страницы ──────────────────────────────────────────────────────────

@app.get("/", response_class=HTMLResponse)
async def index(request: Request, user: Optional[User] = Depends(get_current_user_optional)):
    """Главная страница — редирект на кейсы"""
    if user:
        return RedirectResponse(url="/cases")
    
    return templates.TemplateResponse("index.html", {
        "request": request,
        "user": None,
        "active_page": "home"
    })

@app.get("/login", response_class=HTMLResponse)
async def login_page(request: Request, user: Optional[User] = Depends(get_current_user_optional)):
    """Страница входа"""
    if user:
        return RedirectResponse(url="/profile")
    
    return templates.TemplateResponse("login.html", {
        "request": request,
        "user": None,
        "active_page": "login"
    })

@app.get("/register", response_class=HTMLResponse)
async def register_page(request: Request, user: Optional[User] = Depends(get_current_user_optional)):
    """Страница регистрации"""
    if user:
        return RedirectResponse(url="/profile")
    
    return templates.TemplateResponse("register.html", {
        "request": request,
        "user": None,
        "active_page": "register"
    })

@app.get("/profile", response_class=HTMLResponse)
async def profile_page(
    request: Request,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    total_items = db.query(InventoryItem).filter(InventoryItem.user_id == user.id).count()
    cases_opened = db.query(CaseOpening).filter(CaseOpening.user_id == user.id).count()
    contracts_completed = db.query(Contract).filter(Contract.user_id == user.id).count()
    
    recent_openings = db.query(CaseOpening).filter(
        CaseOpening.user_id == user.id
    ).order_by(desc(CaseOpening.opened_at)).limit(10).all()
    
    recent_contracts = db.query(Contract).filter(
        Contract.user_id == user.id
    ).order_by(desc(Contract.created_at)).limit(10).all()
    
    rarity_order = {k: v for k, v in RARITY_ORDER.items()}
    
    inventory_items = db.query(InventoryItem).filter(
        InventoryItem.user_id == user.id
    ).order_by(desc(InventoryItem.obtained_at)).all()
    
    valuable_items = sorted(
        inventory_items,
        key=lambda x: rarity_order.get(x.rarity, 0),
        reverse=True
    )[:12]
    
    rarities = set()
    types = set()
    enriched_items = []
    for item in inventory_items:
        item_data = None
        for idx, it in enumerate(ALL_ITEMS):
            if it.get("_id", idx) == item.item_id:
                item_data = it
                break
        if not item_data:
            item_data = get_item(item.item_id)
        image_url = ""
        price_rub = 100
        if item_data:
            image_url = item_data.get("image_url", "")
            price_rub = item_data.get("price_rub", 100)
        enriched_items.append({
            "id": item.id,
            "item_id": item.item_id,
            "name": item.market_hash_name,
            "rarity": item.rarity,
            "wear": item.wear,
            "float": item.float_value,
            "type": item.type,
            "image_url": image_url,
            "price_rub": price_rub
        })
        if item.rarity:
            rarities.add(item.rarity)
        if item.type:
            types.add(item.type)
    
    return templates.TemplateResponse("profile.html", {
        "request": request,
        "user": user,
        "total_items": total_items,
        "cases_opened": cases_opened,
        "contracts_completed": contracts_completed,
        "balance": user.balance,
        "recent_openings": recent_openings,
        "recent_contracts": recent_contracts,
        "valuable_items": valuable_items,
        "inventory_items": enriched_items,
        "rarities": sorted(rarities),
        "types": sorted(types),
        "rarity_order": RARITY_ORDER,
        "is_public": False,
        "profile_user": user,
        "active_page": "profile"
    })

# ═══════════════════════════════════════════════════════════════════════════════
# 👤 Публичные профили
# ═══════════════════════════════════════════════════════════════════════════════

@app.get("/profiles/{profile_id}", response_class=HTMLResponse)
async def public_profile_page(
    profile_id: int,
    request: Request,
    db: Session = Depends(get_db),
    current_user: Optional[User] = Depends(get_current_user_optional)
):
    """Публичный профиль пользователя"""
    profile_user = db.query(User).filter(User.id == profile_id).first()
    if not profile_user:
        raise HTTPException(404, detail="Пользователь не найден")
    
    total_items = db.query(InventoryItem).filter(InventoryItem.user_id == profile_user.id).count()
    cases_opened = db.query(CaseOpening).filter(CaseOpening.user_id == profile_user.id).count()
    contracts_completed = db.query(Contract).filter(Contract.user_id == profile_user.id).count()
    
    recent_openings = db.query(CaseOpening).filter(
        CaseOpening.user_id == profile_user.id
    ).order_by(desc(CaseOpening.opened_at)).limit(10).all()
    
    recent_contracts = db.query(Contract).filter(
        Contract.user_id == profile_user.id
    ).order_by(desc(Contract.created_at)).limit(10).all()
    
    inventory_items = db.query(InventoryItem).filter(
        InventoryItem.user_id == profile_user.id
    ).order_by(desc(InventoryItem.obtained_at)).all()
    
    rarity_order = {k: v for k, v in RARITY_ORDER.items()}
    
    rarities = set()
    types = set()
    enriched_items = []
    for item in inventory_items:
        item_data = None
        for idx, it in enumerate(ALL_ITEMS):
            if it.get("_id", idx) == item.item_id:
                item_data = it
                break
        if not item_data:
            item_data = get_item(item.item_id)
        image_url = ""
        price_rub = 100
        if item_data:
            image_url = item_data.get("image_url", "")
            price_rub = item_data.get("price_rub", 100)
        enriched_items.append({
            "id": item.id,
            "item_id": item.item_id,
            "name": item.market_hash_name,
            "rarity": item.rarity,
            "wear": item.wear,
            "float": item.float_value,
            "type": item.type,
            "image_url": image_url,
            "price_rub": price_rub
        })
        if item.rarity:
            rarities.add(item.rarity)
        if item.type:
            types.add(item.type)
    
    valuable_items = sorted(
        inventory_items,
        key=lambda x: rarity_order.get(x.rarity, 0),
        reverse=True
    )[:12]
    
    return templates.TemplateResponse("profile.html", {
        "request": request,
        "user": current_user,
        "profile_user": profile_user,
        "is_public": True,
        "total_items": total_items,
        "cases_opened": cases_opened,
        "contracts_completed": contracts_completed,
        "recent_openings": recent_openings,
        "recent_contracts": recent_contracts,
        "valuable_items": valuable_items,
        "inventory_items": enriched_items,
        "rarities": sorted(rarities),
        "types": sorted(types),
        "rarity_order": RARITY_ORDER,
        "active_page": "profile"
    })


@app.get("/web/api/profiles/{profile_id}/inventory")
async def api_public_profile_inventory(
    profile_id: int,
    db: Session = Depends(get_db),
    current_user: Optional[User] = Depends(get_current_user_optional)
):
    """Публичный инвентарь пользователя (только для просмотра)"""
    profile_user = db.query(User).filter(User.id == profile_id).first()
    if not profile_user:
        return JSONResponse(status_code=404, content={"error": "Пользователь не найден"})
    
    items = db.query(InventoryItem).filter(
        InventoryItem.user_id == profile_user.id
    ).order_by(desc(InventoryItem.obtained_at)).all()
    
    result = []
    for item in items:
        item_data = get_item(item.item_id)
        result.append({
            "id": item.id,
            "item_id": item.item_id,
            "name": item.market_hash_name,
            "rarity": item.rarity,
            "wear": item.wear,
            "float": item.float_value,
            "type": item.type,
            "image_url": item_data.get("image_url", "") if item_data else "",
        })
    
    return JSONResponse({"success": True, "items": result, "username": profile_user.username})


@app.get("/cases", response_class=HTMLResponse)
async def cases_page(
    request: Request,
    user: Optional[User] = Depends(get_current_user_optional)
):
    """Страница с кейсами по секциям"""
    
    #print(f"MAINPAGE_CONFIG: {MAINPAGE_CONFIG}")
    #print(f"CASES_CONFIG keys: {list(CASES_CONFIG.keys())}")
    
    # Подготавливаем данные для каждой секции
    sections = []
    for section in MAINPAGE_CONFIG:
        section_cases = []
        for case_name in section.get('cases', []):
            case_config = CASES_CONFIG.get(case_name, {})
            
            # Получаем предметы кейса
            items = get_case_items(case_name)
            
            # Считаем статистику
            rarities = set()
            for item in items:
                rarities.add(item.get("rarity", "Unknown"))
            
            case_data = {
                "name": case_name,
                "display_name": case_config.get('display_name', case_name),
                "price": case_config.get('price_open', 250),
                "items_count": len(items),
                "rarities": list(rarities),
                "image_url": case_config.get('image_url', ''),
                "description": case_config.get('description', f'Содержит {len(items)} предметов')
            }
            section_cases.append(case_data)
            #print(f"  Добавлен кейс: {case_name} ({len(items)} предметов)")
        
        if section_cases:
            sections.append({
                "tab": section.get('tab', 'Кейсы'),
                "cases": section_cases
            })
    
    #print(f"Всего секций для отображения: {len(sections)}")
    
    return templates.TemplateResponse("cases.html", {
        "request": request,
        "user": user,
        "sections": sections,
        "active_page": "cases"
    })


@app.get("/contracts", response_class=HTMLResponse)
async def contracts_page(
    request: Request,
    db: Session = Depends(get_db),
    user: Optional[User] = Depends(get_current_user_optional)
):
    inventory_items = []
    all_items = []
    knife_items = []
    has_contract = False
    has_knife_contract = False
    
    if user:
        inventory_items = db.query(InventoryItem).filter(
            InventoryItem.user_id == user.id
        ).all()
        
        for inv_item in inventory_items:
            item_data = get_item(inv_item.item_id)
            if not item_data:
                continue
            
            rarity = item_data.get("rarity", "Unknown")
            item_type = inv_item.type or "Normal"
            is_knife_item = is_knife(item_data)
            
            entry = {
                "inventory_id": inv_item.id,
                "item_id": inv_item.item_id,
                "name": inv_item.market_hash_name,
                "float": inv_item.float_value,
                "rarity": rarity,
                "type": item_type,
                "image_url": item_data.get("image_url", ""),
                "is_knife": is_knife_item,
                "craftable": is_item_craftable(inv_item.item_id) or is_knife_item
            }
            if is_knife_item:
                knife_items.append(entry)
            elif is_item_craftable(inv_item.item_id):
                all_items.append(entry)
        
        all_items.sort(key=lambda x: RARITY_ORDER.get(x["rarity"], 99))
        
        # проверяем есть ли 10+ одинаковых по (rarity, type) в all_items
        from collections import Counter
        group_counts = Counter((i["rarity"], i["type"]) for i in all_items)
        has_contract = any(c >= 10 for c in group_counts.values())
        has_knife_contract = len(knife_items) >= 10
    
    return templates.TemplateResponse("contracts.html", {
        "request": request,
        "user": user,
        "inventory_items": all_items + knife_items,
        "has_contract": has_contract or has_knife_contract,
        "knife_count": len(knife_items),
        "active_page": "contracts"
    })

@app.get("/inventory")
async def inventory_redirect():
    return RedirectResponse(url="/profile")

# ─── API эндпоинты для веб-интерфейса ─────────────────────────────────────

@app.post("/web/api/auth/register")
async def api_register(
    request: Request,
    username: str = Form(...),
    password: str = Form(...),
    db: Session = Depends(get_db)
):
    """Регистрация нового пользователя"""
    
    # Проверяем существует ли пользователь
    existing_user = db.query(User).filter(User.username == username).first()
    if existing_user:
        return JSONResponse(
            status_code=400,
            content={"error": "Username already exists"}
        )
    
    if len(username) < 3:
        return JSONResponse(
            status_code=400,
            content={"error": "Username must be at least 3 characters"}
        )
    
    if len(password) < 4:
        return JSONResponse(
            status_code=400,
            content={"error": "Password must be at least 4 characters"}
        )
    
    # Создаем пользователя
    user = create_user(db, username, password)
    
    # Создаем токен
    access_token = create_access_token(data={"sub": user.username})
    
    response = JSONResponse(content={"success": True, "redirect": "/profile"})
    response.set_cookie(
        key="access_token",
        value=f"Bearer {access_token}",
        httponly=True,
        max_age=ACCESS_TOKEN_EXPIRE_MINUTES * 60,
        samesite="lax"
    )
    
    return response

@app.post("/web/api/auth/login")
async def api_login(
    request: Request,
    username: str = Form(...),
    password: str = Form(...),
    db: Session = Depends(get_db)
):
    """Вход в систему"""
    
    user = authenticate_user(db, username, password)
    if not user:
        return JSONResponse(
            status_code=401,
            content={"error": "Invalid username or password"}
        )
    
    # Обновляем время последнего входа
    user.last_login = datetime.utcnow()
    db.commit()
    
    # Создаем токен
    access_token = create_access_token(data={"sub": user.username})
    
    response = JSONResponse(content={"success": True, "redirect": "/profile"})
    response.set_cookie(
        key="access_token",
        value=f"Bearer {access_token}",
        httponly=True,
        max_age=ACCESS_TOKEN_EXPIRE_MINUTES * 60,
        samesite="lax"
    )
    
    return response

@app.post("/web/api/auth/logout")
async def api_logout():
    """Выход из системы"""
    response = JSONResponse(content={"success": True, "redirect": "/"})
    response.delete_cookie("access_token")
    return response


def open_case_with_rpu(case_name: str, adjusted_weights: dict = None) -> dict:
    """Открывает кейс с учетом РПУ весов"""
    case_items = []
    
    search_name = f"Case: {case_name}"
    if search_name in COLLECTIONS_INDEX:
        for idx in COLLECTIONS_INDEX[search_name]:
            item = get_item(idx)
            if item:
                case_items.append((idx, item))
    
    if not case_items:
        return None
    
    items_with_weights = []
    for idx, item in case_items:
        rarity = item.get("rarity", "Unknown")
        
        if adjusted_weights:
            weight = adjusted_weights.get(rarity, CASE_RARITY_WEIGHTS.get(rarity, 100))
        else:
            weight = CASE_RARITY_WEIGHTS.get(rarity, 100)
        
        items_with_weights.append((idx, item, weight))
    
    total_weight = sum(w for _, _, w in items_with_weights)
    rand_val = random.uniform(0, total_weight)
    
    cumsum = 0
    chosen_item = None
    chosen_idx = None
    
    for idx, item, weight in items_with_weights:
        cumsum += weight
        if rand_val <= cumsum:
            chosen_item = item
            chosen_idx = idx
            break
    
    if not chosen_item:
        chosen_idx, chosen_item = items_with_weights[-1][:2]
    
    # Находим правильный ID и image_url
    real_item_id = chosen_idx
    real_image_url = chosen_item.get("image_url", "")
    
    # Ищем в ALL_ITEMS для точности
    for it in ALL_ITEMS:
        if it.get("market_hash_name") == chosen_item.get("market_hash_name"):
            real_image_url = it.get("image_url", real_image_url)
            break
    
    return {
        "id": real_item_id,
        "item": chosen_item,
        "float": generate_item_float(chosen_item.get("rarity", "Unknown")),
        "image_url": real_image_url
    }

@app.post("/web/api/cases/open")
async def api_open_case(
    case_name: str = Form(...),
    count: int = Form(1),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    """Открытие кейса с проверкой баланса и учетом РПУ"""
    
    # Определяем максимальное количество в зависимости от типа кейса
    is_farm = "фарм" in case_name.lower() or "farm" in case_name.lower()
    max_allowed = 100 if is_farm else 10
    
    if count < 1 or count > max_allowed:
        return JSONResponse(status_code=400, content={"error": f"Количество должно быть от 1 до {max_allowed}"})
    
    case_config = CASES_CONFIG.get(case_name, {})
    price_per_case = case_config.get('price_open', 250)
    total_price = price_per_case * count
    
    if user.balance < total_price:
        return JSONResponse(status_code=400, content={
            "error": f"Недостаточно средств. Нужно {total_price:.2f} ₽, у вас {user.balance:.2f} ₽"
        })
    
    # Получаем веса с учетом РПУ
    adjusted_weights = get_adjusted_weights(db, user.id)
    
    results = []
    for _ in range(count):
        result = open_case_with_rpu(case_name, adjusted_weights)
        if result:
            item_data = result["item"]
            item_float = result["float"]
            item_id = result["id"]
            image_url = result.get("image_url", item_data.get("image_url", ""))
            
            if item_id == 0:
                for idx, it in enumerate(ALL_ITEMS):
                    if it == item_data:
                        item_id = idx
                        image_url = it.get("image_url", image_url)
                        break
            
            inventory_item = InventoryItem(
                user_id=user.id,
                item_id=item_id or 0,
                market_hash_name=item_data.get("market_hash_name", "Unknown"),
                rarity=item_data.get("rarity", "Unknown"),
                wear=item_data.get("wear", "Unknown"),
                float_value=item_float,
                type=item_data.get("type", "Normal"),
                obtained_from=f"case: {case_name}"
            )
            db.add(inventory_item)
            
            opening = CaseOpening(
                user_id=user.id,
                case_name=case_name,
                item_id=item_id or 0,
                item_name=item_data.get("market_hash_name", "Unknown"),
                rarity=item_data.get("rarity", "Unknown"),
                float_value=item_float
            )
            db.add(opening)
            
            results.append({
                "id": item_id or 0,
                "name": item_data.get("market_hash_name"),
                "rarity": item_data.get("rarity"),
                "float": item_float,
                "image_url": image_url,
                "wear": item_data.get("wear", "Unknown")
            })
    
    user.balance -= total_price
    update_rpu_stats(db, user.id, total_price, count)
    db.commit()
    
    # Проверка достижений
    unlocked_ach = []
    unlocked_ach += check_open_cases_achievements(db, user.id)
    total_spent = db.query(CaseOpening).filter(CaseOpening.user_id == user.id).count() * price_per_case
    unlocked_ach += check_spent_achievements(db, user.id, total_spent)
    
    # Проверка на редкий предмет
    for r in results:
        unlocked_ach += check_rare_item_achievement(db, user.id, r.get("rarity", ""))
        item_data = get_item(r.get("id", 0))
        if item_data:
            unlocked_ach += check_knife_achievement(db, user.id, item_data)
    
    unlocked_ach += check_inventory_achievement(db, user.id)
    
    # Запись активности — каждый предмет отдельно
    for r in results:
        act = record_activity_own_session(user.id, user.username, "case_open",
            f"📦 открыл(а) кейс и получил(а) {r['name']}",
            {"case": case_name, "item": r["name"], "rarity": r.get("rarity", ""), "item_image": r.get("image_url", "")})
        try:
            asyncio.ensure_future(manager.broadcast({
                "type": "activity",
                "activity": act
            }))
        except:
            pass
    
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "inventory_update"}))
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "balance_update", "balance": user.balance}))
    
    return JSONResponse({
        "success": True,
        "results": results,
        "count": len(results),
        "spent": total_price,
        "new_balance": user.balance,
        "achievements_unlocked": unlocked_ach if unlocked_ach else None
    })


@app.post("/web/api/contracts/submit")
async def api_submit_contract(
    inventory_ids: str = Form(...),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    try:
        ids = json.loads(inventory_ids)
    except:
        return JSONResponse(status_code=400, content={"error": "Invalid inventory IDs"})
    
    if len(ids) != 10:
        return JSONResponse(status_code=400, content={"error": "Exactly 10 items required"})
    
    input_items_data = []
    inventory_items = []
    all_knives = True
    
    for inv_id in ids:
        inv_item = db.query(InventoryItem).filter(
            InventoryItem.id == inv_id,
            InventoryItem.user_id == user.id
        ).first()
        
        if not inv_item:
            return JSONResponse(status_code=400, content={"error": f"Item {inv_id} not found"})
        
        item_data = get_item(inv_item.item_id)
        if not item_data:
            return JSONResponse(status_code=400, content={"error": f"Item data not found"})
        
        if not is_knife(item_data):
            all_knives = False
        
        item_with_float = item_data.copy()
        item_with_float["mid_float_used"] = inv_item.float_value
        
        input_items_data.append(item_with_float)
        inventory_items.append(inv_item)
    
    # Проверка: если есть и ножи и не-ножи — ошибка
    if not all_knives and any(is_knife(get_item(it.item_id)) for it in inventory_items):
        return JSONResponse(status_code=400, content={"error": "Нельзя мешать ножи с обычными предметами"})
    if not all_knives:
        for inv_item in inventory_items:
            if not is_item_craftable(inv_item.item_id):
                return JSONResponse(status_code=400, content={"error": f"Предмет {inv_item.market_hash_name} нельзя использовать в контракте"})
    
    # Крафт дилдоков из ножей
    if all_knives and len(inventory_items) == 10:
        for inv_item in inventory_items:
            db.delete(inv_item)
        db.commit()
        
        dildo_items = [it for it in ALL_ITEMS if it.get("weapon") == "Dildo"]
        if not dildo_items:
            return JSONResponse(status_code=500, content={"error": "Дилдоки кончились :("})
        chosen = random.choice(dildo_items)
        chosen_id = next(idx for idx, it in enumerate(ALL_ITEMS) if it is chosen)
        
        dildo_item = {
            "success": True,
            "received_item": {
                "id": chosen_id,
                "name": chosen.get("market_hash_name", "Dildo"),
                "rarity": chosen.get("rarity", "Covert"),
                "float": round(generate_item_float(chosen.get("rarity", "Covert")), 6),
                "probability": 100.0,
                "image_url": chosen.get("image_url", "/static/placeholder.png")
            },
            "achievements_unlocked": None,
            "is_knife_contract": True
        }
        
        act_data = {"item": chosen.get("market_hash_name", "Dildo"), "rarity": chosen.get("rarity", "Covert")}
        act = record_activity_own_session(user.id, user.username, "contract",
            f"🔞 скрафтил(а) {chosen.get('market_hash_name', 'дилдок')} из 10 ножей", act_data)
        try:
            asyncio.ensure_future(manager.broadcast({"type": "activity", "activity": act}))
        except:
            pass
        asyncio.ensure_future(manager.send_personal(user.id, {"type": "inventory_update"}))
        return JSONResponse(content=dildo_item)
    
    result = simulate_trade_up(input_items_data)
    
    if not result.success:
        return JSONResponse(status_code=400, content={"error": result.message})
    
    # Бан: принудительно выдаём самый дешёвый возможный предмет
    if user.is_banned:
        possible_outcomes, _ = get_possible_outcomes(input_items_data)
        if possible_outcomes:
            cheapest_idx = min(
                possible_outcomes,
                key=lambda x: (get_item(x[0]) or {}).get("price_rub", float('inf'))
            )[0]
            forced_item = get_item(cheapest_idx)
            if forced_item:
                from backend import ItemShort
                result.received_item = ItemShort(
                    id=cheapest_idx,
                    market_hash_name=forced_item.get("market_hash_name", "Unknown"),
                    rarity=forced_item.get("rarity", "Unknown"),
                    wear=forced_item.get("wear", "Unknown"),
                    image_url=forced_item.get("image_url", ""),
                    price_rub=forced_item.get("price_rub", 0)
                )
                result.received_float = round(calculate_average_float(input_items_data), 4)
                result.probability = 0.01
    
    for inv_item in inventory_items:
        db.delete(inv_item)
    
    received_item_data = get_item(result.received_item.id)
    
    new_inventory_item = InventoryItem(
        user_id=user.id,
        item_id=result.received_item.id,
        market_hash_name=result.received_item.market_hash_name,
        rarity=result.received_item.rarity,
        wear=received_item_data.get("wear", "Unknown"),
        float_value=result.received_float,
        type=received_item_data.get("type", "Normal"),
        obtained_from="contract"
    )
    db.add(new_inventory_item)
    
    contract = Contract(
        user_id=user.id,
        input_item_ids=json.dumps(ids),
        output_item_id=result.received_item.id,
        output_item_name=result.received_item.market_hash_name,
        output_float=result.received_float,
        probability=result.probability
    )
    db.add(contract)
    
    db.commit()
    
    unlocked_ach = check_contracts_achievements(db, user.id)
    unlocked_ach += check_inventory_achievement(db, user.id)
    
    act = record_activity_own_session(user.id, user.username, "contract",
        f"🔄 выполнил(а) контракт и получил(а) {result.received_item.market_hash_name}",
        {"item": result.received_item.market_hash_name, "rarity": result.received_item.rarity, "item_image": received_item_data.get("image_url", "")})
    try:
        asyncio.ensure_future(manager.broadcast({
            "type": "activity",
            "activity": act
        }))
    except:
        pass
    
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "inventory_update"}))
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "balance_update", "balance": user.balance}))
    
    return JSONResponse({
        "success": True,
        "received_item": {
            "id": result.received_item.id,
            "name": result.received_item.market_hash_name,
            "rarity": result.received_item.rarity,
            "float": result.received_float,
            "probability": result.probability,
            "image_url": received_item_data.get("image_url", "")
        },
        "achievements_unlocked": unlocked_ach if unlocked_ach else None
    })

@app.get("/web/api/inventory/items")
async def api_get_inventory(
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    """Получение предметов инвентаря"""
    
    items = db.query(InventoryItem).filter(
        InventoryItem.user_id == user.id
    ).order_by(desc(InventoryItem.obtained_at)).all()
    
    result = []
    for item in items:
        item_data = get_item(item.item_id)
        result.append({
            "id": item.id,
            "item_id": item.item_id,
            "name": item.market_hash_name,
            "rarity": item.rarity,
            "wear": item.wear,
            "float": item.float_value,
            "type": item.type,
            "price_rub": item_data.get("price_rub", 100) if item_data else 100,
            "image_url": item_data.get("image_url", "") if item_data else "",
            "obtained_from": item.obtained_from,
            "obtained_at": item.obtained_at.isoformat() if item.obtained_at else None
        })
    
    return result


@app.get("/web/api/item/{item_id}/image")
async def get_item_image(
    item_id: int,
    user: User = Depends(get_current_user)
):
    """Получение URL картинки предмета"""
    item = get_item(item_id)
    if not item:
        return JSONResponse(
            status_code=404,
            content={"error": "Item not found"}
        )
    
    image_url = item.get("image_url", "")
    return {
        "item_id": item_id,
        "image_url": image_url,
        "has_image": bool(image_url)
    }


def get_case_items(case_name: str) -> List[dict]:
    """Получает предметы кейса по его имени"""
    items = []
    
    # Сначала проверяем cases.json
    case_config = CASES_CONFIG.get(case_name)
    if case_config and case_config.get('items'):
        # Загружаем предметы по ID из конфига
        for item_id in case_config.get('items', []):
            item = get_item(item_id)
            if item:
                items.append(item)
        print(f"[DEBUG] Загружено {len(items)} предметов из cases.json для кейса {case_name}")
    
    # Если предметов нет, ищем через COLLECTIONS_INDEX (старый метод)
    if not items:
        search_name = f"Case: {case_name}"
        if search_name in COLLECTIONS_INDEX:
            for idx in COLLECTIONS_INDEX[search_name]:
                item = get_item(idx)
                if item:
                    items.append(item)
            print(f"[DEBUG] Загружено {len(items)} предметов из COLLECTIONS_INDEX для кейса {case_name}")
    
    return items


def get_case_items_grouped(case_name: str) -> List[dict]:
    """Получает сгруппированные предметы кейса (по базовому имени)"""
    
    # Получаем все предметы кейса
    all_items = get_case_items(case_name)
    if not all_items:
        return []
    
    # Группируем по базовому имени (без качества и StatTrak/Souvenir)
    grouped = {}
    
    for item in all_items:
        # Извлекаем базовое имя (без (Factory New) и т.д.)
        name = item.get("market_hash_name", "")
        
        # Убираем префиксы
        base_name = name
        for prefix in ["StatTrak™ ", "Souvenir "]:
            if base_name.startswith(prefix):
                base_name = base_name[len(prefix):]
        
        # Убираем качество в скобках
        if " (" in base_name:
            base_name = base_name.split(" (")[0]
        
        # Ключ группировки: базовое имя + тип + редкость
        item_type = item.get("type", "Normal")
        rarity = item.get("rarity", "Unknown")
        group_key = f"{base_name}|{item_type}|{rarity}"
        
        if group_key not in grouped:
            # Находим все варианты этого скина
            variants = []
            min_price = float('inf')
            max_price = 0
            
            for variant in all_items:
                variant_name = variant.get("market_hash_name", "")
                variant_base = variant_name
                for prefix in ["StatTrak™ ", "Souvenir "]:
                    if variant_base.startswith(prefix):
                        variant_base = variant_base[len(prefix):]
                if " (" in variant_base:
                    variant_base = variant_base.split(" (")[0]
                
                if variant_base == base_name and variant.get("type") == item_type:
                    variants.append(variant)
                    price = variant.get("price_rub", 0)
                    if price < min_price:
                        min_price = price
                    if price > max_price:
                        max_price = price
            
            # Определяем диапазон float
            float_values = [v.get("mid_float_used", 0) for v in variants if v.get("mid_float_used")]
            min_float = min(float_values) if float_values else 0.01
            max_float = max(float_values) if float_values else 0.45
            
            grouped[group_key] = {
                "base_name": base_name,
                "display_name": base_name,
                "type": item_type,
                "rarity": rarity,
                "variants": variants,
                "variants_count": len(variants),
                "min_price": min_price if min_price != float('inf') else 0,
                "max_price": max_price,
                "min_float": min_float,
                "max_float": max_float,
                "image_url": item.get("image_url", ""),
                "wear_range": f"{min_float:.2f} - {max_float:.2f}"
            }
    
    return list(grouped.values())


def open_case_with_variants(case_name: str) -> dict:
    """Открывает кейс и возвращает предмет с случайным float"""
    
    grouped_items = get_case_items_grouped(case_name)
    if not grouped_items:
        return None
    
    # Взвешенный выбор по редкости (шанс на группу)
    items_with_weights = []
    for group in grouped_items:
        rarity = group["rarity"]
        weight = CASE_RARITY_WEIGHTS.get(rarity, 100)
        items_with_weights.append((group, weight))
    
    total_weight = sum(w for _, w in items_with_weights)
    rand_val = random.uniform(0, total_weight)
    
    cumsum = 0
    chosen_group = None
    for group, weight in items_with_weights:
        cumsum += weight
        if rand_val <= cumsum:
            chosen_group = group
            break
    
    if not chosen_group:
        chosen_group = items_with_weights[-1][0]
    
    # Выбираем случайный вариант из группы (или генерируем новый)
    variants = chosen_group["variants"]
    
    if variants:
        # Выбираем случайный вариант как основу
        base_item = random.choice(variants)
    else:
        return None
    
    # Генерируем случайный float в диапазоне
    min_float = chosen_group["min_float"]
    max_float = chosen_group["max_float"]
    item_float = round(random.uniform(min_float, max_float), 6)
    
    # Определяем wear по float
    wear = get_wear_from_float(item_float)
    
    # Создаем итоговый предмет
    result_item = base_item.copy()
    result_item["float_value"] = item_float
    result_item["wear"] = wear
    result_item["mid_float_used"] = item_float
    
    return {
        "id": base_item.get("_id", 0),
        "item": result_item,
        "float": item_float,
        "group": chosen_group
    }

def get_wear_from_float(float_value: float) -> str:
    """Определяет качество по float"""
    if float_value < 0.07:
        return "Factory New"
    elif float_value < 0.15:
        return "Minimal Wear"
    elif float_value < 0.38:
        return "Field-Tested"
    elif float_value < 0.45:
        return "Well-Worn"
    else:
        return "Battle-Scarred"


@app.get("/web/api/user/balance")
async def get_user_balance(
    user: User = Depends(get_current_user)
):
    """Получение текущего баланса пользователя"""
    return JSONResponse({
        "success": True,
        "balance": user.balance
    })


@app.post("/web/api/cases/open/animated")
async def api_open_case_animated(
    case_name: str = Form(...),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    """Открытие кейса с анимацией (возвращает результат)"""
    
    result = open_case(case_name)
    if not result:
        return JSONResponse(
            status_code=400,
            content={"error": "Case not found or empty"}
        )
    
    item_data = result["item"]
    item_float = result["float"]
    
    # Добавляем предмет в инвентарь
    inventory_item = InventoryItem(
        user_id=user.id,
        item_id=result["id"],
        market_hash_name=item_data.get("market_hash_name", "Unknown"),
        rarity=item_data.get("rarity", "Unknown"),
        wear=item_data.get("wear", "Unknown"),
        float_value=item_float,
        type=item_data.get("type", "Normal"),
        obtained_from=f"case: {case_name}"
    )
    db.add(inventory_item)
    
    # Записываем открытие
    opening = CaseOpening(
        user_id=user.id,
        case_name=case_name,
        item_id=result["id"],
        item_name=item_data.get("market_hash_name", "Unknown"),
        rarity=item_data.get("rarity", "Unknown"),
        float_value=item_float
    )
    db.add(opening)
    
    db.commit()
    
    return JSONResponse({
        "success": True,
        "item": {
            "id": result["id"],
            "name": item_data.get("market_hash_name"),
            "rarity": item_data.get("rarity"),
            "float": item_float,
            "image_url": item_data.get("image_url", ""),
            "wear": item_data.get("wear", "Unknown")
        }
    })

@app.get("/static/placeholder.png")
async def get_placeholder():
    """Возвращает placeholder изображение"""
    placeholder_path = Path("static/placeholder.png")
    if placeholder_path.exists():
        return FileResponse(placeholder_path)
    
    # Возвращаем SVG если файла нет
    from fastapi.responses import Response
    svg = '''<svg xmlns="http://www.w3.org/2000/svg" width="200" height="150" viewBox="0 0 200 150">
        <rect width="200" height="150" fill="#334155"/>
        <rect x="10" y="10" width="180" height="130" fill="#1e293b" rx="8"/>
        <text x="100" y="80" text-anchor="middle" fill="#94a3b8" font-size="16" font-family="Arial">🎁</text>
        <text x="100" y="105" text-anchor="middle" fill="#64748b" font-size="12" font-family="Arial">No image</text>
    </svg>'''
    return Response(content=svg, media_type="image/svg+xml")


@app.get("/case/{case_name}", response_class=HTMLResponse)
async def case_detail_page(
    case_name: str,
    request: Request,
    user: Optional[User] = Depends(get_current_user_optional)
):
    """Страница конкретного кейса с группировкой скинов"""
    
    case_config = CASES_CONFIG.get(case_name, {})
    
    # Получаем сгруппированные предметы
    grouped_items = get_case_items_grouped(case_name)
    
    if not grouped_items:
        raise HTTPException(404, detail="Кейс не найден или пуст")
    
    # Группируем по редкости для отображения
    items_by_rarity = {}
    rarities_count = {}
    total_weight = 0
    
    for group in grouped_items:
        rarity = group["rarity"]
        weight = CASE_RARITY_WEIGHTS.get(rarity, 100)
        total_weight += weight
        
        if rarity not in items_by_rarity:
            items_by_rarity[rarity] = []
        
        item_data = {
            "name": group["display_name"],
            "rarity": rarity,
            "type": group["type"],
            "image_url": group["image_url"],
            "min_price": group["min_price"],
            "max_price": group["max_price"],
            "min_float": group["min_float"],
            "max_float": group["max_float"],
            "wear_range": group["wear_range"],
            "variants_count": group["variants_count"],
            "weight": weight,
            "probability": 0
        }
        items_by_rarity[rarity].append(item_data)
        rarities_count[rarity] = rarities_count.get(rarity, 0) + 1
    
    # Считаем вероятности
    for rarity in items_by_rarity:
        for item in items_by_rarity[rarity]:
            item["probability"] = (item["weight"] / total_weight) * 100
    
    sorted_rarities = sorted(items_by_rarity.keys(), 
                            key=lambda x: RARITY_ORDER.get(x, 0))
    
    # Для анимации - список всех возможных групп
    case_items_json = []
    for rarity in items_by_rarity:
        for item in items_by_rarity[rarity]:
            case_items_json.append({
                "name": item["name"],
                "rarity": item["rarity"],
                "image_url": item["image_url"]
            })
    
    return templates.TemplateResponse("case_detail.html", {
        "request": request,
        "user": user,
        "case_name": case_name,
        "case_display_name": case_config.get('display_name', case_name),
        "case_price": case_config.get('price_open', 250),
        "case_image": case_config.get('image_url', ''),
        "case_description": case_config.get('description', ''),
        "total_items": len(grouped_items),
        "items_by_rarity": items_by_rarity,
        "sorted_rarities": sorted_rarities,
        "rarities_count": rarities_count,
        "case_items": case_items_json,
        "active_page": "cases"
    })


def open_case_with_variants_rpu(case_name: str, adjusted_weights: dict = None) -> dict:
    """Открывает кейс с вариантами и учетом РПУ"""
    
    grouped_items = get_case_items_grouped(case_name)
    if not grouped_items:
        return None
    
    items_with_weights = []
    for group in grouped_items:
        rarity = group["rarity"]
        if adjusted_weights:
            weight = adjusted_weights.get(rarity, CASE_RARITY_WEIGHTS.get(rarity, 100))
        else:
            weight = CASE_RARITY_WEIGHTS.get(rarity, 100)
        items_with_weights.append((group, weight))
    
    total_weight = sum(w for _, w in items_with_weights)
    rand_val = random.uniform(0, total_weight)
    
    cumsum = 0
    chosen_group = None
    for group, weight in items_with_weights:
        cumsum += weight
        if rand_val <= cumsum:
            chosen_group = group
            break
    
    if not chosen_group:
        chosen_group = items_with_weights[-1][0]
    
    variants = chosen_group["variants"]
    
    if variants:
        base_item = random.choice(variants)
    else:
        return None
    
    # Находим реальный ID предмета в ALL_ITEMS
    real_item_id = None
    for idx, it in enumerate(ALL_ITEMS):
        if it.get("market_hash_name") == base_item.get("market_hash_name"):
            real_item_id = it.get("_id", idx)
            break
    
    if real_item_id is None:
        real_item_id = base_item.get("_id", 0)
    
    # Получаем правильный image_url из ALL_ITEMS
    real_image_url = base_item.get("image_url", "")
    if not real_image_url:
        for it in ALL_ITEMS:
            if it.get("_id", 0) == real_item_id:
                real_image_url = it.get("image_url", "")
                break
    
    min_float = chosen_group["min_float"]
    max_float = chosen_group["max_float"]
    item_float = round(random.uniform(min_float, max_float), 6)
    
    wear = get_wear_from_float(item_float)
    
    result_item = base_item.copy()
    result_item["float_value"] = item_float
    result_item["wear"] = wear
    result_item["mid_float_used"] = item_float
    result_item["image_url"] = real_image_url  # Добавляем правильный URL
    
    return {
        "id": real_item_id,
        "item": result_item,
        "float": item_float,
        "group": chosen_group
    }


def pick_slot_reel(grouped_items: list, adjusted_weights: dict, exclude_groups: set = None) -> tuple:
    """Выбирает один предмет для барабана слот-машины"""
    items_with_weights = []
    for group in grouped_items:
        if exclude_groups and group["base_name"] in exclude_groups:
            continue
        rarity = group["rarity"]
        weight = adjusted_weights.get(rarity, CASE_RARITY_WEIGHTS.get(rarity, 100))
        items_with_weights.append((group, weight))
    
    if not items_with_weights:
        return None, None
    
    total_weight = sum(w for _, w in items_with_weights)
    rand_val = random.uniform(0, total_weight)
    
    cumsum = 0
    chosen_group = None
    for group, weight in items_with_weights:
        cumsum += weight
        if rand_val <= cumsum:
            chosen_group = group
            break
    
    if not chosen_group:
        chosen_group = items_with_weights[-1][0]
    
    variants = chosen_group["variants"]
    base_item = random.choice(variants) if variants else None
    return chosen_group, base_item


@app.post("/web/api/cases/open/slot")
async def api_open_slot_case(
    case_name: str = Form(...),
    free_spin: str = Form(""),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    """Слот-машина: 3 барабана, совпадение = приз"""

    case_config = CASES_CONFIG.get(case_name, {})
    price_per_case = case_config.get('price_open', 250)

    is_free = free_spin == "1"

    if not is_free and user.balance < price_per_case:
        return JSONResponse(status_code=400, content={
            "error": f"Недостаточно средств. Нужно {price_per_case:.2f} ₽"
        })
    
    grouped_items = get_case_items_grouped(case_name)
    if not grouped_items or len(grouped_items) < 2:
        return JSONResponse(status_code=400, content={"error": "В кейсе недостаточно предметов"})
    
    adjusted_weights = get_adjusted_weights(db, user.id)
    
    # Баним: принудительно разные группы на всех барабанах
    if user.is_banned:
        group1, item1 = pick_slot_reel(grouped_items, adjusted_weights)
        group2, item2 = pick_slot_reel(grouped_items, adjusted_weights, {group1["base_name"]})
        group3, item3 = pick_slot_reel(grouped_items, adjusted_weights, {group1["base_name"], group2["base_name"]})
        if not item3:
            group3, item3 = pick_slot_reel(grouped_items, adjusted_weights, {group1["base_name"]})
    else:
        group1, item1 = pick_slot_reel(grouped_items, adjusted_weights)
        group2, item2 = pick_slot_reel(grouped_items, adjusted_weights)
        group3, item3 = pick_slot_reel(grouped_items, adjusted_weights)
    
    def resolve_reel(group, item):
        if not item or not group:
            return None
        real_id = None
        for idx, it in enumerate(ALL_ITEMS):
            if it.get("market_hash_name") == item.get("market_hash_name"):
                real_id = it.get("_id", idx)
                break
        return {
            "id": real_id or item.get("_id", 0),
            "name": item.get("market_hash_name", "???"),
            "image_url": item.get("image_url", ""),
            "rarity": group["rarity"],
            "base_name": group["base_name"]
        }
    
    reels = [resolve_reel(group1, item1), resolve_reel(group2, item2), resolve_reel(group3, item3)]
    reels = [r for r in reels if r]
    
    # Assign activations to each reel's target item
    ACTIVATION_WEIGHTS = [
        ("extra_spin", 0.03),
        ("double", 0.01),
        ("guaranteed", 0.02),
        ("free_spin", 0.02),
        ("quintuple", 0.005),
    ]
    def pick_activation():
        r = random.random()
        cum = 0
        for name, w in ACTIVATION_WEIGHTS:
            cum += w
            if r < cum:
                return name
        return ""
    
    reel_activations = [pick_activation() for _ in range(3)]
    
    match = False
    prize_data = None
    bonus_data = None
    prize_price = 0
    bonus_price = 0
    multiplier = 1  # 1 = normal, 2 = 2x, 5 = 5x
    multiplied_prize = None
    
    if len(reels) == 3:
        base_names = [r["base_name"] for r in reels]
        match = len(set(base_names)) == 1

    def make_item(item_data, item_id, base_name, wear, item_float, source):
        display_name = f"{base_name} ({wear})"
        return InventoryItem(
            user_id=user.id, item_id=item_id,
            market_hash_name=display_name,
            rarity=item_data.get("rarity", "Unknown"),
            wear=wear, float_value=item_float,
            type=item_data.get("type", "Normal"),
            obtained_from=source
        )

    if match:
        # Check for multiplier activation on any reel
        for act in reel_activations:
            if act == "double":
                multiplier = 2
                break
            elif act == "quintuple":
                multiplier = 5
                break
        
        # Находим реальный предмет для приза
        matched_name = reels[0]["name"]
        prize_item = None
        for g in grouped_items:
            if g["base_name"] == reels[0]["base_name"]:
                variants = g["variants"]
                prize_item = random.choice(variants) if variants else None
                break
        
        if prize_item:
            prize_float = round(random.uniform(0.01, 0.45), 6)
            prize_wear = "Field-Tested"
            prize_id = None
            for idx, it in enumerate(ALL_ITEMS):
                if it.get("market_hash_name") == prize_item.get("market_hash_name"):
                    prize_id = it.get("_id", idx)
                    break
            
            prize_price = prize_item.get("price_rub", 0)
            
            # Бонус: ближайший предмет стоимостью ≥ 1.5× цены кейса
            target_min_price = price_per_case * 1.5
            candidates = []
            for g in grouped_items:
                for v in g["variants"]:
                    vp = v.get("price_rub", 0)
                    if vp >= target_min_price:
                        candidates.append((vp, v, g))
            
            if candidates:
                candidates.sort(key=lambda x: x[0])
                bonus_price, bonus_variant, bonus_group = candidates[0]
                bonus_float = round(random.uniform(0.01, 0.45), 6)
                bonus_wear = "Field-Tested"
                bonus_id = None
                for idx, it in enumerate(ALL_ITEMS):
                    if it.get("market_hash_name") == bonus_variant.get("market_hash_name"):
                        bonus_id = it.get("_id", idx)
                        break
                
                bonus_data = {
                    "id": bonus_id or bonus_variant.get("_id", 0),
                    "name": bonus_variant.get("market_hash_name", "???"),
                    "image_url": bonus_variant.get("image_url", ""),
                    "rarity": bonus_group["rarity"],
                    "price": bonus_price,
                    "float": bonus_float,
                    "wear": bonus_wear
                }
            
            # If multiplier > 1: find a single item worth (prize+bonus) * multiplier
            if multiplier > 1:
                total_value = (prize_price + bonus_price) * multiplier
                # Find nearest item in ALL_ITEMS to this value
                best_item = None
                best_diff = float('inf')
                for idx, it in enumerate(ALL_ITEMS):
                    ip = it.get("price_rub", 0)
                    if ip <= 0:
                        continue
                    diff = abs(ip - total_value)
                    if diff < best_diff:
                        best_diff = diff
                        best_item = it
                
                if best_item:
                    multiplied_id = None
                    for idx, it in enumerate(ALL_ITEMS):
                        if it.get("market_hash_name") == best_item.get("market_hash_name"):
                            multiplied_id = it.get("_id", idx)
                            break
                    multiplied_prize = {
                        "id": multiplied_id or best_item.get("_id", 0),
                        "name": best_item.get("market_hash_name", "???"),
                        "image_url": best_item.get("image_url", ""),
                        "rarity": best_item.get("rarity", "Unknown"),
                        "price": best_item.get("price_rub", 0),
                        "float": round(random.uniform(0.01, 0.45), 6),
                        "wear": "Field-Tested"
                    }
            
            prize_data = {
                "id": prize_id or prize_item.get("_id", 0),
                "name": prize_item.get("market_hash_name", "???"),
                "image_url": prize_item.get("image_url", ""),
                "rarity": prize_item.get("rarity", "Unknown"),
                "price": prize_price,
                "float": prize_float,
                "wear": prize_wear
            }

            if multiplied_prize:
                mult_item_data = {"rarity": multiplied_prize["rarity"], "type": "Normal"}
                db.add(make_item(mult_item_data, multiplied_prize["id"],
                    multiplied_prize["name"].rsplit(" (", 1)[0] if " (" in multiplied_prize["name"] else multiplied_prize["name"],
                    multiplied_prize["wear"], multiplied_prize["float"],
                    f"slot_mult{multiplier}: {case_name}"))
                db.add(CaseOpening(
                    user_id=user.id, case_name=case_name,
                    item_id=multiplied_prize["id"],
                    item_name=multiplied_prize["name"],
                    rarity=multiplied_prize["rarity"],
                    float_value=multiplied_prize["float"]
                ))
            else:
                db.add(make_item(prize_item, prize_data["id"], reels[0]["base_name"],
                                prize_data["wear"], prize_data["float"], f"slot: {case_name}"))
                db.add(CaseOpening(
                    user_id=user.id, case_name=case_name,
                    item_id=prize_data["id"],
                    item_name=prize_data["name"],
                    rarity=prize_data["rarity"],
                    float_value=prize_data["float"]
                ))

                if bonus_data:
                    bonus_item_data = {"rarity": bonus_data["rarity"], "type": "Normal"}
                    db.add(make_item(bonus_item_data, bonus_data["id"],
                                    bonus_data["name"].rsplit(" (", 1)[0] if " (" in bonus_data["name"] else bonus_data["name"],
                                    bonus_data["wear"], bonus_data["float"], f"slot_bonus: {case_name}"))
                    db.add(CaseOpening(
                        user_id=user.id, case_name=case_name,
                        item_id=bonus_data["id"],
                        item_name=bonus_data["name"],
                        rarity=bonus_data["rarity"],
                        float_value=bonus_data["float"]
                    ))

    elif "guaranteed" in reel_activations:
        # No match but guaranteed activation: give consolation prize = cheapest affordable item
        guaranteed_idx = reel_activations.index("guaranteed")
        guessed_group, guessed_item = pick_slot_reel(grouped_items, adjusted_weights)
        if guessed_item and guessed_group:
            g_price = guessed_item.get("price_rub", 0)
            if g_price > 0:
                prize_price = g_price
                prize_data = {
                    "id": guessed_item.get("_id", 0),
                    "name": guessed_item.get("market_hash_name", "???"),
                    "image_url": guessed_item.get("image_url", ""),
                    "rarity": guessed_group["rarity"],
                    "price": prize_price,
                    "float": round(random.uniform(0.01, 0.45), 6),
                    "wear": "Field-Tested"
                }
                db.add(make_item(
                    {"rarity": guessed_group["rarity"], "type": "Normal"},
                    prize_data["id"],
                    guessed_group["base_name"],
                    prize_data["wear"], prize_data["float"],
                    f"slot_guaranteed: {case_name}"
                ))
                db.add(CaseOpening(
                    user_id=user.id, case_name=case_name,
                    item_id=prize_data["id"],
                    item_name=prize_data["name"],
                    rarity=prize_data["rarity"],
                    float_value=prize_data["float"]
                ))
    
    if not is_free:
        user.balance -= price_per_case
        update_rpu_stats(db, user.id, price_per_case, 1)
    
    # Проверка достижений для слота
    unlocked_ach = []
    if match:
        unlocked_ach += check_slot_match_achievement(db, user.id)
    unlocked_ach += check_slot_plays_achievements(db, user.id)
    
    db.commit()
    
    return JSONResponse({
        "success": True,
        "achievements_unlocked": unlocked_ach if unlocked_ach else None,
        "reels": reels,
        "match": match,
        "prize": prize_data,
        "bonus": bonus_data,
        "prize_value": prize_price,
        "bonus_value": bonus_price,
        "spent": 0 if is_free else price_per_case,
        "activations": reel_activations,
        "multiplier": multiplier if multiplier > 1 else 0,
        "multiplied_prize": multiplied_prize
    })


@app.post("/web/api/cases/open/multiple")
async def api_open_multiple_cases(
    case_name: str = Form(...),
    count: int = Form(...),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    """Открытие нескольких кейсов с группировкой и РПУ"""
    
    # Определяем максимальное количество в зависимости от типа кейса
    is_slot = "slot" in case_name.lower() or "слот" in case_name.lower()
    is_farm = "фарм" in case_name.lower() or "farm" in case_name.lower()
    max_allowed = 1 if is_slot else (100 if is_farm else 10)
    
    if count < 1 or count > max_allowed:
        return JSONResponse(status_code=400, content={
            "error": f"Количество должно быть от 1 до {max_allowed}"
        })
    
    case_config = CASES_CONFIG.get(case_name, {})
    price_per_case = case_config.get('price_open', 250)
    total_price = price_per_case * count
    
    if user.balance < total_price:
        return JSONResponse(status_code=400, content={
            "error": f"Недостаточно средств. Нужно {total_price:.2f} ₽, у вас {user.balance:.2f} ₽"
        })
    
    # Подготовка для бана: ищем самый дешёвый предмет в кейсе
    cheapest_case_item = None
    if user.is_banned:
        for group in get_case_items_grouped(case_name):
            for v in group["variants"]:
                v_price = v.get("price_rub", 0)
                if cheapest_case_item is None or v_price < cheapest_case_item["price"]:
                    v_id = None
                    for idx, it in enumerate(ALL_ITEMS):
                        if it.get("market_hash_name") == v.get("market_hash_name"):
                            v_id = it.get("_id", idx)
                            break
                    cheapest_case_item = {
                        "id": v_id or v.get("_id", 0),
                        "item": v,
                        "price": v_price,
                        "base_name": group["base_name"],
                        "rarity": group["rarity"]
                    }
    
    # Получаем веса с учетом РПУ
    adjusted_weights = get_adjusted_weights(db, user.id)
    
    results = []
    for _ in range(count):
        result = open_case_with_variants_rpu(case_name, adjusted_weights)
        if result:
            item_data = result["item"]
            item_float = result["float"]
            item_id = result["id"]
            base_name = result["group"]["base_name"]
            
            # Бан: выдаём самый дешёвый предмет если выпал дороже 75% цены кейса
            if user.is_banned and cheapest_case_item:
                item_price = item_data.get("price_rub", 0)
                max_allowed_price = price_per_case * 0.75
                if item_price > max_allowed_price:
                    ci = cheapest_case_item
                    item_data = ci["item"]
                    item_id = ci["id"]
                    item_float = round(random.uniform(0.01, 0.45), 6)
                    base_name = ci["base_name"]
                    wear = "Field-Tested"
            
            wear = item_data.get("wear", "Field-Tested")
            display_name = f"{base_name} ({wear})"
            if item_data.get("type") == "StatTrak":
                display_name = f"StatTrak™ {display_name}"
            elif item_data.get("type") == "Souvenir":
                display_name = f"Souvenir {display_name}"
            
            inventory_item = InventoryItem(
                user_id=user.id,
                item_id=item_id,
                market_hash_name=display_name,
                rarity=item_data.get("rarity", "Unknown"),
                wear=wear,
                float_value=item_float,
                type=item_data.get("type", "Normal"),
                obtained_from=f"case: {case_name}"
            )
            db.add(inventory_item)
            
            opening = CaseOpening(
                user_id=user.id,
                case_name=case_name,
                item_id=item_id,
                item_name=display_name,
                rarity=item_data.get("rarity", "Unknown"),
                float_value=item_float
            )
            db.add(opening)
            
            price = item_data.get("price_rub", 0)
            
            results.append({
                "id": item_id,
                "name": display_name,
                "rarity": item_data.get("rarity"),
                "float": item_float,
                "image_url": item_data.get("image_url", ""),
                "wear": wear,
                "price": price
            })
    
    user.balance -= total_price
    
    # Обновляем статистику РПУ
    update_rpu_stats(db, user.id, total_price, count)
    
    db.commit()
    
    # Запись активности — каждый предмет отдельно
    for r in results:
        act = record_activity_own_session(user.id, user.username, "case_open",
            f"📦 открыл(а) кейс и получил(а) {r['name']}",
            {"case": case_name, "item": r["name"], "rarity": r.get("rarity", ""), "item_image": r.get("image_url", "")})
        try:
            asyncio.ensure_future(manager.broadcast({
                "type": "activity",
                "activity": act
            }))
        except:
            pass
    
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "inventory_update"}))
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "balance_update", "balance": user.balance}))
    
    return JSONResponse({
        "success": True,
        "results": results,
        "count": len(results),
        "spent": total_price,
        "new_balance": user.balance
    })



@app.post("/web/api/inventory/sell")
async def api_sell_item(
    inventory_id: int = Form(...),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    """Продажа предмета из инвентаря"""
    
    inv_item = db.query(InventoryItem).filter(
        InventoryItem.id == inventory_id,
        InventoryItem.user_id == user.id
    ).first()
    
    if not inv_item:
        return JSONResponse(status_code=400, content={"error": "Предмет не найден"})
    
    # Получаем цену предмета
    item_data = get_item(inv_item.item_id)
    if not item_data:
        return JSONResponse(status_code=400, content={"error": "Данные предмета не найдены"})
    
    price = item_data.get('price_rub', 0)
    if price <= 0:
        price = 100  # Минимальная цена если не указана
    
    # Начисляем деньги
    user.balance += price
    
    # Удаляем предмет
    db.delete(inv_item)
    db.commit()
    
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "inventory_update"}))
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "balance_update", "balance": user.balance}))
    
    return JSONResponse({
        "success": True,
        "sold_price": price,
        "new_balance": user.balance,
        "message": f"Предмет продан за {price:.2f} ₽"
    })



@app.post("/web/api/inventory/sell/bulk")
async def api_sell_bulk_items(
    inventory_ids: str = Form(...),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    """Массовая продажа предметов из инвентаря"""
    
    try:
        ids = json.loads(inventory_ids)
    except:
        return JSONResponse(status_code=400, content={"error": "Неверный формат ID"})
    
    if not ids:
        return JSONResponse(status_code=400, content={"error": "Не выбрано ни одного предмета"})
    
    # Получаем предметы
    items_to_sell = db.query(InventoryItem).filter(
        InventoryItem.id.in_(ids),
        InventoryItem.user_id == user.id
    ).all()
    
    if not items_to_sell:
        return JSONResponse(status_code=400, content={"error": "Предметы не найдены"})
    
    total_received = 0
    sold_count = 0
    
    for inv_item in items_to_sell:
        # Получаем цену предмета
        item_data = get_item(inv_item.item_id)
        if item_data:
            price = item_data.get('price_rub', 100)
            if price <= 0:
                price = 100
        else:
            price = 100
        
        total_received += price
        sold_count += 1
        
        # Удаляем предмет
        db.delete(inv_item)
    
    # Начисляем деньги
    user.balance += total_received
    db.commit()
    
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "inventory_update"}))
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "balance_update", "balance": user.balance}))
    
    return JSONResponse({
        "success": True,
        "sold_count": sold_count,
        "total_received": total_received,
        "new_balance": user.balance
    })


@app.get("/upgrade", response_class=HTMLResponse)
async def upgrade_page(
    request: Request,
    db: Session = Depends(get_db),
    user: Optional[User] = Depends(get_current_user_optional)
):
    """Страница апгрейдов"""
    
    enriched_items = []
    upgrade_rpu = None
    total_attempts = 0
    total_success = 0
    success_rate = 0
    recent_upgrades = []
    
    if user:
        # Получаем предметы из инвентаря
        inventory_items_db = db.query(InventoryItem).filter(
            InventoryItem.user_id == user.id
        ).order_by(desc(InventoryItem.obtained_at)).all()
        
        # Обогащаем предметы данными из ALL_ITEMS
        for item in inventory_items_db:
            item_data = None
            for idx, it in enumerate(ALL_ITEMS):
                if it.get("_id", idx) == item.item_id:
                    item_data = it
                    break
            
            if not item_data:
                item_data = get_item(item.item_id)
            
            if item_data:
                enriched_items.append({
                    "id": item.id,
                    "item_id": item.item_id,
                    "name": item.market_hash_name,
                    "rarity": item.rarity,
                    "price_rub": item_data.get("price_rub", 100),
                    "image_url": item_data.get("image_url", ""),
                    "wear": item.wear,
                    "float": item.float_value
                })
        
        # Получаем РПУ для апгрейдов
        upgrade_rpu = get_upgrade_rpu(db, user.id)
        
        # Статистика
        total_attempts = upgrade_rpu.total_attempts
        total_success = upgrade_rpu.total_success
        success_rate = (total_success / total_attempts * 100) if total_attempts > 0 else 0
        
        # Последние апгрейды
        recent_rows = db.query(Upgrade).filter(
            Upgrade.user_id == user.id
        ).order_by(desc(Upgrade.created_at)).limit(20).all()
        
        for u in recent_rows:
            input_img = u.input_item_image_url or ""
            target_img = u.target_item_image_url or ""
            if not input_img:
                idata = get_item(u.input_item_id)
                if idata: input_img = idata.get("image_url", "")
            if not target_img:
                tdata = get_item(u.target_item_id)
                if tdata: target_img = tdata.get("image_url", "")

            recent_upgrades.append({
                "id": u.id,
                "success": u.success,
                "input_item_id": u.input_item_id,
                "input_item_name": u.input_item_name or "",
                "input_image_url": input_img,
                "target_item_id": u.target_item_id,
                "target_item_name": u.target_item_name or "",
                "target_image_url": target_img,
                "probability": round(u.rpu_adjusted_probability or u.probability or 0, 1)
            })
    
    return templates.TemplateResponse("upgrade.html", {
        "request": request,
        "user": user,
        "inventory_items": enriched_items,
        "upgrade_rpu": upgrade_rpu,
        "total_attempts": total_attempts,
        "total_success": total_success,
        "success_rate": round(success_rate, 1),
        "recent_upgrades": recent_upgrades,
        "active_page": "upgrade"
    })

@app.post("/web/api/upgrade/calculate")
async def api_calculate_upgrade(
    input_inventory_id: int = Form(...),
    target_item_id: int = Form(...),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    """Рассчитать шанс апгрейда"""
    
    # Получаем входной предмет
    input_item = db.query(InventoryItem).filter(
        InventoryItem.id == input_inventory_id,
        InventoryItem.user_id == user.id
    ).first()
    
    if not input_item:
        return JSONResponse(status_code=400, content={"error": "Предмет не найден"})
    
    # Получаем данные предметов из ALL_ITEMS
    input_data = None
    for idx, it in enumerate(ALL_ITEMS):
        if it.get("_id", idx) == input_item.item_id:
            input_data = it
            break
    
    target_data = None
    for idx, it in enumerate(ALL_ITEMS):
        if it.get("_id", idx) == target_item_id:
            target_data = it
            break
    
    if not input_data:
        input_data = get_item(input_item.item_id)
    if not target_data:
        target_data = get_item(target_item_id)
    
    if not input_data or not target_data:
        return JSONResponse(status_code=400, content={"error": "Данные предметов не найдены"})
    
    input_price = input_data.get("price_rub", 100)
    target_price = target_data.get("price_rub", 100)

    if target_price <= input_price:
        return JSONResponse(status_code=400, content={
            "error": f"Нельзя апгрейдить в более дешёвый предмет! ({input_price:.0f} ₽ → {target_price:.0f} ₽)"
        })

    base_probability = calculate_upgrade_probability(input_price, target_price)
    adjusted_probability = get_adjusted_upgrade_probability(db, user.id, base_probability)

    return JSONResponse({
        "success": True,
        "input_item": {
            "id": input_item.id,
            "name": input_item.market_hash_name,
            "price": input_price,
            "image_url": input_data.get("image_url", "")
        },
        "target_item": {
            "id": target_item_id,
            "name": target_data.get("market_hash_name", "Unknown"),
            "price": target_price,
            "image_url": target_data.get("image_url", "")
        },
        "base_probability": round(base_probability, 2),
        "adjusted_probability": round(adjusted_probability, 2),
        "rpu_multiplier": get_upgrade_rpu(db, user.id).upgrade_multiplier
    })

@app.get("/web/api/upgrade/history")
async def api_upgrade_history(
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    recent = db.query(Upgrade).filter(
        Upgrade.user_id == user.id
    ).order_by(desc(Upgrade.created_at)).limit(20).all()

    items = []
    for u in recent:
        input_img = u.input_item_image_url or ""
        target_img = u.target_item_image_url or ""
        if not input_img:
            idata = get_item(u.input_item_id)
            if idata: input_img = idata.get("image_url", "")
        if not target_img:
            tdata = get_item(u.target_item_id)
            if tdata: target_img = tdata.get("image_url", "")

        items.append({
            "id": u.id,
            "success": u.success,
            "input_item_id": u.input_item_id,
            "input_item_name": u.input_item_name or "",
            "input_image_url": input_img,
            "target_item_id": u.target_item_id,
            "target_item_name": u.target_item_name or "",
            "target_image_url": target_img,
            "probability": round(u.rpu_adjusted_probability or u.probability or 0, 1)
        })

    return JSONResponse({"success": True, "history": items})


@app.post("/web/api/upgrade/execute")
async def api_execute_upgrade(
    input_inventory_id: int = Form(...),
    target_item_id: int = Form(...),
    quick_mode: bool = Form(False),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    input_item = db.query(InventoryItem).filter(
        InventoryItem.id == input_inventory_id,
        InventoryItem.user_id == user.id
    ).first()

    if not input_item:
        return JSONResponse(status_code=400, content={"error": "Предмет не найден"})

    input_data = None
    for idx, it in enumerate(ALL_ITEMS):
        if it.get("_id", idx) == input_item.item_id:
            input_data = it
            break

    target_data = None
    for idx, it in enumerate(ALL_ITEMS):
        if it.get("_id", idx) == target_item_id:
            target_data = it
            break

    if not input_data:
        input_data = get_item(input_item.item_id)
    if not target_data:
        target_data = get_item(target_item_id)

    if not input_data or not target_data:
        return JSONResponse(status_code=400, content={"error": "Данные предметов не найдены"})

    input_price = input_data.get("price_rub", 100)
    target_price = target_data.get("price_rub", 100)

    if target_price <= input_price:
        return JSONResponse(status_code=400, content={
            "error": f"Нельзя апгрейдить в более дешёвый предмет!"
        })

    base_probability = calculate_upgrade_probability(input_price, target_price)
    adjusted_probability = get_adjusted_upgrade_probability(db, user.id, base_probability)
    
    # Бан: колесо падает аккурат на 1-15° после зелёной зоны (как будто был шанс)
    if user.is_banned:
        half_green = (adjusted_probability / 100) * 180
        green_end = 180 + half_green
        target_angle = min(360, green_end + random.uniform(1, 15))
        rotations = random.randint(1, 3) if quick_mode else random.randint(6, 8)
        final_angle = target_angle + rotations * 360
        success = False
    else:
        success, final_angle, rotations, _ = spin_upgrade_wheel(adjusted_probability)
        if quick_mode:
            rotations = random.randint(1, 3)
            final_angle = (final_angle % 360) + rotations * 360
    # Сохраняем поля до удаления
    saved_input_name = input_item.market_hash_name
    saved_input_item_id = input_item.item_id
    db.delete(input_item)

    if success:
        item_float = generate_item_float(target_data.get("rarity", "Unknown"))
        new_item = InventoryItem(
            user_id=user.id,
            item_id=target_item_id,
            market_hash_name=target_data.get("market_hash_name", "Unknown"),
            rarity=target_data.get("rarity", "Unknown"),
            wear=target_data.get("wear", "Unknown"),
            float_value=item_float,
            type=target_data.get("type", "Normal"),
            obtained_from="upgrade"
        )
        db.add(new_item)

    # Записываем апгрейд
    upgrade = Upgrade(
        user_id=user.id,
        input_item_id=input_item.item_id,
        input_item_name=saved_input_name,
        input_item_image_url=input_data.get("image_url", ""),
        target_item_id=target_item_id,
        target_item_name=target_data.get("market_hash_name", "Unknown"),
        target_item_image_url=target_data.get("image_url", ""),
        success=success,
        probability=base_probability if not user.is_banned else calculate_upgrade_probability(input_price, target_price),
        rpu_adjusted_probability=adjusted_probability
    )
    db.add(upgrade)
    
    # Обновляем статистику
    update_upgrade_stats(db, user.id, success, input_price)
    
    db.commit()
    
    unlocked_ach = check_upgrade_achievements(db, user.id)
    
    if success:
        act = record_activity_own_session(user.id, user.username, "upgrade",
            f"✅ выиграл(а) апгрейд: {saved_input_name} → {target_data.get('market_hash_name', '???')}",
            {"input": saved_input_name, "target": target_data.get("market_hash_name", ""), "success": True, "item_image": target_data.get("image_url", "")})
        try:
            asyncio.ensure_future(manager.broadcast({
                "type": "activity",
                "activity": act
            }))
        except:
            pass
    
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "inventory_update"}))
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "balance_update", "balance": user.balance}))
    
    return JSONResponse({
        "success": True,
        "upgrade_success": success,
        "final_angle": final_angle,
        "rotations": rotations,
        "probability": round(adjusted_probability, 2),
        "input_item": {
            "name": input_item.market_hash_name,
            "image_url": input_data.get("image_url", ""),
            "price": input_price,
            "rarity": input_item.rarity
        },
        "received_item": {
            "name": target_data.get("market_hash_name"),
            "image_url": target_data.get("image_url", ""),
            "price": target_price,
            "rarity": target_data.get("rarity")
        } if success else None,
        "achievements_unlocked": unlocked_ach if unlocked_ach else None
    })

# ═══════════════════════════════════════════════════════════════════════════════
# 🏆 Достижения
# ═══════════════════════════════════════════════════════════════════════════════

@app.get("/achievements", response_class=HTMLResponse)
async def achievements_page(
    request: Request,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    achievements = get_user_achievements(db, user.id)
    total = len(achievements)
    unlocked = sum(1 for a in achievements if a["unlocked"])
    
    return templates.TemplateResponse("achievements.html", {
        "request": request,
        "user": user,
        "achievements": achievements,
        "total": total,
        "unlocked": unlocked,
        "active_page": "achievements"
    })


@app.get("/web/api/achievements")
async def api_get_achievements(
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    return JSONResponse({
        "success": True,
        "achievements": get_user_achievements(db, user.id)
    })


# ═══════════════════════════════════════════════════════════════════════════════
# 📰 Лента активностей
# ═══════════════════════════════════════════════════════════════════════════════

@app.get("/activity", response_class=HTMLResponse)
async def activity_page(
    request: Request,
    db: Session = Depends(get_db),
    user: Optional[User] = Depends(get_current_user_optional)
):
    activities = db.query(ActivityFeed).order_by(desc(ActivityFeed.created_at)).limit(50).all()
    
    return templates.TemplateResponse("activity.html", {
        "request": request,
        "user": user,
        "activities": [
            {
                "id": a.id,
                "user_id": a.user_id,
                "username": a.username,
                "activity_type": a.activity_type,
                "message": a.message,
                "data": json.loads(a.data_json) if a.data_json else {},
                "created_at": a.created_at.isoformat()
            }
            for a in activities
        ],
        "active_page": "activity"
    })


@app.get("/web/api/activity")
async def api_get_activity(
    limit: int = 50,
    db: Session = Depends(get_db),
    user: Optional[User] = Depends(get_current_user_optional)
):
    activities = db.query(ActivityFeed).order_by(desc(ActivityFeed.created_at)).limit(limit).all()
    
    return JSONResponse({
        "success": True,
        "activities": [
            {
                "id": a.id,
                "user_id": a.user_id,
                "username": a.username,
                "activity_type": a.activity_type,
                "message": a.message,
                "data": json.loads(a.data_json) if a.data_json else {},
                "created_at": a.created_at.isoformat()
            }
            for a in activities
        ]
    })


# ═══════════════════════════════════════════════════════════════════════════════
# 💥 Crash Game
# ═══════════════════════════════════════════════════════════════════════════════

async def crash_game_loop():
    while True:
        g = crash_game
        g.round += 1
        g.phase = "betting"
        g.timer = 12
        g.bets = {}
        g.cashed_out = {}
        
        while g.timer > 0:
            await manager.broadcast({
                "type": "crash_state",
                "game": g.to_dict()
            })
            await asyncio.sleep(1)
            g.timer -= 1
        
        if not g.bets:
            g.phase = "waiting"
            await asyncio.sleep(2)
            continue
        
        g.phase = "running"
        g.crash_point = g.generate_crash_point()
        g.current_multiplier = 1.0
        g._start_time = asyncio.get_event_loop().time()
        
        while True:
            g._elapsed = asyncio.get_event_loop().time() - g._start_time
            g.current_multiplier = g.get_multiplier_at_time(g._elapsed)
            
            if g.current_multiplier >= g.crash_point:
                g.current_multiplier = g.crash_point
                break
            
            await manager.broadcast({
                "type": "crash_state",
                "game": g.to_dict()
            })
            
            # Чем выше множитель, тем реже обновляем (для производительности)
            if g.current_multiplier < 2:
                await asyncio.sleep(0.03)
            elif g.current_multiplier < 5:
                await asyncio.sleep(0.05)
            elif g.current_multiplier < 10:
                await asyncio.sleep(0.08)
            else:
                await asyncio.sleep(0.12)
        
        g.phase = "crashed"
        await manager.broadcast({
            "type": "crash_state",
            "game": g.to_dict()
        })
        
        # Выплаты тем кто вывел
        for uid, cashout_mult in g.cashed_out.items():
            bet = g.bets.get(uid, 0)
            if bet > 0:
                from database import User as CrUser
                db_c = next(get_db())
                u = db_c.query(CrUser).filter(CrUser.id == uid).first()
                if u:
                    payout = g.calculate_payout(bet, cashout_mult)
                    try:
                        u.balance += payout
                        db_c.commit()
                    except:
                        db_c.rollback()
                    
                    act = record_activity_own_session(uid, u.username, "crash",
                        f"💥 {u.username} выиграл(а) {payout:.0f} ₽ в Crash (x{cashout_mult})",
                        {"payout": payout, "multiplier": cashout_mult})
                    
                    try:
                        asyncio.ensure_future(manager.broadcast({
                            "type": "activity",
                            "activity": act
                        }))
                    except:
                        pass
                db_c.close()
        
        g.history.append(g.crash_point)
        g.phase = "waiting"
        await asyncio.sleep(4)


@app.on_event("startup")
async def startup():
    asyncio.create_task(crash_game_loop())


@app.get("/crash", response_class=HTMLResponse)
async def crash_page(
    request: Request,
    user: User = Depends(get_current_user)
):
    return templates.TemplateResponse("crash.html", {
        "request": request,
        "user": user,
        "active_page": "crash"
    })


@app.post("/web/api/crash/bet")
async def api_crash_bet(
    amount: float = Form(...),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    g = crash_game
    if g.phase != "betting":
        return JSONResponse(status_code=400, content={"error": "Ставки закрыты"})
    if amount <= 0:
        return JSONResponse(status_code=400, content={"error": "Неверная сумма"})
    if user.balance < amount:
        return JSONResponse(status_code=400, content={"error": "Недостаточно средств"})
    
    user.balance -= amount
    g.bets[user.id] = g.bets.get(user.id, 0) + amount
    
    # Проверка достижений
    from database import ActivityFeed as CrActivity
    crash_count = db.query(CrActivity).filter(
        CrActivity.user_id == user.id,
        CrActivity.activity_type == "crash"
    ).count() + 1
    unlocked_ach = []
    r1 = check_achievement(db, user.id, "crash_first", 1)
    if r1: unlocked_ach.append(r1)
    r2 = check_achievement(db, user.id, "crash_pro", crash_count)
    if r2: unlocked_ach.append(r2)
    db.commit()
    
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "balance_update", "balance": user.balance}))
    
    return JSONResponse({
        "success": True,
        "bet": g.bets[user.id],
        "new_balance": user.balance,
        "achievements_unlocked": unlocked_ach if unlocked_ach else None
    })


@app.post("/web/api/crash/cashout")
async def api_crash_cashout(
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user)
):
    g = crash_game
    if g.phase != "running":
        return JSONResponse(status_code=400, content={"error": "Игра не в активной фазе"})
    if user.id not in g.bets:
        return JSONResponse(status_code=400, content={"error": "У вас нет активной ставки"})
    if user.id in g.cashed_out:
        return JSONResponse(status_code=400, content={"error": "Вы уже вывели"})
    
    g.cashed_out[user.id] = g.current_multiplier
    payout = g.calculate_payout(g.bets[user.id], g.current_multiplier)
    
    # Сразу начисляем
    user.balance += payout
    
    # Проверка достижений
    unlocked_ach = []
    if g.current_multiplier >= 5.0:
        r = check_achievement(db, user.id, "crash_5x", 1)
        if r: unlocked_ach.append(r)
    
    db.commit()
    
    asyncio.ensure_future(manager.send_personal(user.id, {"type": "balance_update", "balance": user.balance}))
    
    return JSONResponse({
        "success": True,
        "multiplier": g.current_multiplier,
        "payout": payout,
        "new_balance": user.balance,
        "achievements_unlocked": unlocked_ach if unlocked_ach else None
    })


# ─── Запуск ────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    import uvicorn
    make_first_user_admin()
    reset_pidor_password()
    uvicorn.run(app, host="0.0.0.0", port=13669)
