from dotenv import load_dotenv
from pathlib import Path
ROOT_DIR = Path(__file__).parent
load_dotenv(ROOT_DIR / '.env')

import os
import re
import urllib.request
import uuid
import logging
import bcrypt
import jwt
from datetime import datetime, timezone, timedelta
from typing import List, Optional, Dict, Any
from fastapi import FastAPI, APIRouter, HTTPException, Depends, Request, Query, UploadFile, File
from starlette.middleware.cors import CORSMiddleware
from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseModel, Field, EmailStr, ConfigDict
import asyncio
from forum_ext import build_router, init_storage, monthly_winner_cron, auto_archive_cron
from idg_admin_ext import build_idg_router, seed_idg, rss_importer_cron
from idg_admin_ext_v2 import build_idg_router_v2, seed_idg_v2
from idg_core_routes import build_idg_core_router, build_idg_external_router
from idg_models import create_idg_indexes
from idg_chat_ext import router as idg_chat_router
from idg_clans_ext import router as idg_clans_router
from idg_news_ext import router as idg_news_router
from idg_tournaments_ext import router as idg_tournaments_router
from idg_hltv_ext import router as idg_hltv_router
from idg_downloads_ext import router as idg_downloads_router
from server_query import build_servers_router
from idg_widgets_api import build_widgets_data_router
from idg_arena_ext import build_arena_router
from idg_mcp_ext import build_mcp_router
from idg_stats_ext import router as idg_stats_router

# ---------- Config / DB ----------
mongo_url = os.environ['MONGO_URL']
client = AsyncIOMotorClient(mongo_url)
db = client[os.environ['DB_NAME']]

JWT_SECRET = os.environ['JWT_SECRET']
JWT_ALG = "HS256"
JWT_TTL_HOURS = 24 * 7  # 1 week

app = FastAPI(title="TopFrag Forum API")
app.state.db = db
api = APIRouter(prefix="/api")

@app.get("/")
@app.get("/health")
async def root_health():
    return {"status": "ok", "service": "indungipro_forum_api"}

@api.get("/")
@api.get("/health")
async def api_health():
    return {"status": "ok", "service": "indungipro_forum_api"}


# Caches to optimize query latency and prevent N+1 queries
_groups_cache = None


def clear_groups_cache():
    global _groups_cache
    _groups_cache = None


async def _get_groups_dict(db_instance):
    global _groups_cache
    if _groups_cache is not None:
        return _groups_cache
    try:
        groups = await db_instance.idg_groups.find({}).to_list(1000)
        _groups_cache = {
            "by_id": {g["id"]: g for g in groups if "id" in g},
            "by_slug": {g["slug"]: g for g in groups if "slug" in g}
        }
    except Exception:
        _groups_cache = {"by_id": {}, "by_slug": {}}
    return _groups_cache


async def hydrate_user(user: Dict[str, Any]) -> Dict[str, Any]:
    if not user:
        return user
    for f in ["steam_id", "discord_username", "cs2_rank", "favorite_game"]:
        user.setdefault(f, "")
    
    group_ids = user.get("group_ids") or []
    role = user.get("role") or "user"
    
    # Use cached groups
    groups_data = await _get_groups_dict(db)
    
    active_group = None
    if group_ids:
        # Find matching groups from cache
        matched_groups = [groups_data["by_id"][gid] for gid in group_ids if gid in groups_data["by_id"]]
        if matched_groups:
            matched_groups.sort(key=lambda g: g.get("legend_order", 999))
            active_group = matched_groups[0]
            user["groups"] = [{"id": g["id"], "name": g["name"], "color": g.get("color"), "label_icon": g.get("label_icon"), "description": g.get("description", "")} for g in matched_groups]
            
    if not active_group:
        slug = "member"
        if role == "root":
            slug = "root"
        elif role == "admin":
            slug = "admin"
        elif role == "mod":
            slug = "mod"
        active_group = groups_data["by_slug"].get(slug)
        
    if active_group:
        user["group_name"] = active_group.get("name")
        user["group_color"] = active_group.get("color")
        user["group_style"] = active_group.get("group_style") or ""
        user["group_icon"] = active_group.get("label_icon")
    else:
        user["group_name"] = "Member"
        user["group_color"] = "#71717A"
        user["group_style"] = ""
        user["group_icon"] = "user"
        user.setdefault("groups", [])
        
    return user



# ---------- Helpers ----------
def now_iso():
    return datetime.now(timezone.utc).isoformat()


def hash_password(pw: str) -> str:
    return bcrypt.hashpw(pw.encode(), bcrypt.gensalt()).decode()


def verify_password(pw: str, hashed: str) -> bool:
    try:
        return bcrypt.checkpw(pw.encode(), hashed.encode())
    except Exception:
        return False


def create_token(user_id: str) -> str:
    payload = {
        "sub": user_id,
        "exp": datetime.now(timezone.utc) + timedelta(hours=JWT_TTL_HOURS),
        "iat": datetime.now(timezone.utc),
    }
    return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALG)


def slugify(s: str) -> str:
    import re
    s = s.lower().strip()
    s = re.sub(r"[^a-z0-9]+", "-", s)
    return s.strip("-")[:80] or uuid.uuid4().hex[:8]


def _check_board_post_perms(board: Dict[str, Any], user: Dict[str, Any]):
    if board.get("archived"):
        raise HTTPException(403, "Board is archived (read-only)")
    role_rank = {"user": 0, "mod": 1, "admin": 2, "root": 3}
    min_role = board.get("min_role") or "user"
    user_role = user.get("role") or "user"
    if role_rank.get(user_role, 0) < role_rank.get(min_role, 0):
        raise HTTPException(403, f"This board requires role '{min_role}'")
    if board.get("vip_only") and not user.get("is_vip") and user_role not in ("admin", "root", "mod"):
        raise HTTPException(403, "This board is VIP only")


def clean_user(u: Dict[str, Any]) -> Dict[str, Any]:
    if not u:
        return u
    u = {k: v for k, v in u.items() if k != "password_hash"}
    u.pop("_id", None)
    return u


async def check_user_vip_expiration(user: Dict[str, Any]):
    if not user or not user.get("is_vip") or user.get("role") in ("admin", "root"):
        return user
    exp_str = user.get("vip_expires_at") or user.get("vip_until")
    if exp_str:
        try:
            exp = datetime.fromisoformat(exp_str.replace("Z", "+00:00"))
            if exp < datetime.now(timezone.utc):
                await db.users.update_one(
                    {"id": user["id"]},
                    {"$set": {"is_vip": False, "vip_tier": None, "vip_expired_at": now_iso()}}
                )
                user["is_vip"] = False
                user["vip_tier"] = None
        except Exception:
            pass
    return user


async def get_user_optional(request: Request) -> Optional[Dict[str, Any]]:
    auth = request.headers.get("Authorization", "")
    if not auth.startswith("Bearer "):
        return None
    token = auth[7:]
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALG])
        user = await db.users.find_one({"id": payload["sub"]}, {"_id": 0})
        if user:
            user = await check_user_vip_expiration(user)
        return user
    except Exception:
        return None


async def get_current_user(request: Request) -> Dict[str, Any]:
    user = await get_user_optional(request)
    if not user:
        raise HTTPException(401, "Not authenticated")
    return user


async def require_admin(request: Request) -> Dict[str, Any]:
    user = await get_current_user(request)
    if user.get("role") not in ("admin", "root"):
        raise HTTPException(403, "Admin only")
    return user


class TranslateRequest(BaseModel):
    text: str
    target_lang: str

@api.post("/translate")
async def translate_text(body: TranslateRequest):
    import urllib.request
    import urllib.parse
    import json
    
    # Handle language mapping for Google Translate
    # UI uses ro, en, ru, fr
    lang_map = {
        "ro": "ro",
        "en": "en",
        "ru": "ru",
        "fr": "fr"
    }
    target = lang_map.get(body.target_lang, "en")
    
    try:
        # Encode translation parameters
        query = urllib.parse.urlencode({
            "client": "gtx",
            "sl": "auto",
            "tl": target,
            "dt": "t",
            "q": body.text
        })
        url = f"https://translate.googleapis.com/translate_a/single?{query}"
        
        # User-Agent is required to prevent 403 Forbidden
        req = urllib.request.Request(
            url, 
            headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
        )
        
        with urllib.request.urlopen(req, timeout=5) as response:
            res_data = json.loads(response.read().decode('utf-8'))
            
        # Parse Google Translate nested array response
        # Structure is [[["translated_chunk", "original_chunk", null, null, 1], ...], null, "source_lang"]
        translated_text = ""
        if res_data and len(res_data) > 0 and res_data[0]:
            chunks = []
            for item in res_data[0]:
                if item and len(item) > 0:
                    chunks.append(item[0])
            translated_text = "".join(chunks)
            
        return {"translated_text": translated_text, "source_lang": res_data[2] if len(res_data) > 2 else "auto"}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Translation failed: {str(e)}")


# ---------- Models ----------
class RegisterIn(BaseModel):
    email: EmailStr
    username: str = Field(min_length=2, max_length=24)
    password: str = Field(min_length=4, max_length=128)
    country: Optional[str] = ""
    age: Optional[int] = None
    steam_id: Optional[str] = ""
    referral_source: Optional[str] = ""


class LoginIn(BaseModel):
    email: str
    password: str


class ProfileUpdate(BaseModel):
    model_config = ConfigDict(extra="ignore")
    bio: Optional[str] = None
    location: Optional[str] = None
    age: Optional[int] = None
    referral_source: Optional[str] = None
    signature: Optional[str] = None
    favorite_game: Optional[str] = None
    favorite_weapon: Optional[str] = None
    avatar_url: Optional[str] = None
    banner_url: Optional[str] = None
    language: Optional[str] = None
    profile_widgets: Optional[List[str]] = None


class LoadoutUpdate(BaseModel):
    model_config = ConfigDict(extra="ignore")
    username_style: Optional[str] = None
    avatar_frame: Optional[str] = None
    banner_badge: Optional[str] = None
    card_background: Optional[str] = None
    custom_card_banner: Optional[str] = None
    favorite_weapon: Optional[str] = None
    background_video: Optional[str] = None
    custom_bg_video: Optional[str] = None
    personal_quote: Optional[str] = None
    profile_theme: Optional[str] = None
    profile_song: Optional[str] = None


class MediaImportRequest(BaseModel):
    url: str
    target: Optional[str] = "both"  # "background_video", "card_background", "both"



class BoardIn(BaseModel):
    model_config = ConfigDict(extra="ignore")
    name: str
    description: str = ""
    game: str = "general"
    icon: str = "shield"
    order: int = 0
    parent_id: Optional[str] = None
    min_role: Optional[str] = "user"
    vip_only: Optional[bool] = False
    archived: Optional[bool] = False
    requires_approval: Optional[bool] = False  # Topic-level moderation queue
    background_image_url: Optional[str] = None  # Custom background image for board page
    subforum_columns: Optional[int] = None


class TopicIn(BaseModel):
    board_id: str
    title: str = Field(min_length=3, max_length=160)
    content: str = Field(min_length=1)
    attachments: Optional[List[Dict[str, Any]]] = None


class PostIn(BaseModel):
    topic_id: str
    content: str = Field(min_length=1)
    attachments: Optional[List[Dict[str, Any]]] = None


class ReportIn(BaseModel):
    target_type: str  # "post" or "topic"
    target_id: str
    reason: str = Field(min_length=3, max_length=500)


class ReactionIn(BaseModel):
    type: str = "like"  # like, fire, headshot, ace, clutch


class AdminUserUpdate(BaseModel):
    model_config = ConfigDict(extra="ignore")
    role: Optional[str] = None  # user/mod/admin/root
    is_vip: Optional[bool] = None
    vip_tier: Optional[str] = None
    vip_expires_at: Optional[str] = None
    vip_until: Optional[str] = None
    vip_duration_days: Optional[int] = None
    vip_purchase_count: Optional[int] = None
    points: Optional[int] = None
    banned: Optional[bool] = None
    group_ids: Optional[List[str]] = None
    username: Optional[str] = None
    email: Optional[str] = None
    avatar_url: Optional[str] = None
    banner_url: Optional[str] = None
    bio: Optional[str] = None


class SettingsIn(BaseModel):
    model_config = ConfigDict(extra="ignore")
    site_name: Optional[str] = None
    tagline: Optional[str] = None
    description: Optional[str] = None
    accent_primary: Optional[str] = None
    accent_secondary: Optional[str] = None
    monthly_winner_points: Optional[int] = None
    points_per_post: Optional[int] = None
    points_per_topic: Optional[int] = None
    points_per_reaction: Optional[int] = None
    payment_paysafe_url: Optional[str] = None
    payment_paypal_url: Optional[str] = None
    payment_revolut_url: Optional[str] = None
    donate_description: Optional[str] = None
    slides: Optional[list] = None
    categories: Optional[list] = None
    archive_after_days: Optional[int] = None
    subforum_columns: Optional[int] = Field(default=None, ge=1, le=5)  # 1-5 columns, admin-only


# ---------- Points logic ----------
async def award_points(user_id: str, amount: int, reason: str):
    await db.users.update_one({"id": user_id}, {"$inc": {"points": amount}})
    await db.points_log.insert_one({
        "id": uuid.uuid4().hex,
        "user_id": user_id,
        "amount": amount,
        "reason": reason,
        "created_at": now_iso(),
        "month_key": datetime.now(timezone.utc).strftime("%Y-%m"),
    })
    await check_trophies(user_id)


# Trophy rules: {trophy_id: {threshold, field, condition}}
TROPHIES = {
    "first_post":       ("message_count", 1),
    "ten_posts":        ("message_count", 10),
    "hundred_posts":    ("message_count", 100),
    "five_hundred_posts": ("message_count", 500),
    "first_reaction":   ("reaction_score", 1),
    "fifty_reactions":  ("reaction_score", 50),
    "hundred_reactions": ("reaction_score", 100),
}


async def check_trophies(user_id: str):
    user = await db.users.find_one({"id": user_id}, {"_id": 0, "password_hash": 0})
    if not user:
        return
    current = set(user.get("trophies") or [])
    new = set(current)
    for tid, (field, threshold) in TROPHIES.items():
        if user.get(field, 0) >= threshold:
            new.add(tid)
    # Special trophies based on role/VIP
    if user.get("is_vip"):
        new.add("vip_member")
        if user.get("vip_tier"):
            new.add(user["vip_tier"])
    # Topic count trophies
    topic_count = await db.topics.count_documents({"author_id": user_id})
    if topic_count >= 1:
        new.add("first_topic")
    if topic_count >= 10:
        new.add("ten_topics")
    added = new - current
    if added:
        await db.users.update_one({"id": user_id}, {"$set": {"trophies": sorted(new)}})
        for t in added:
            await db.notifications.insert_one({
                "id": uuid.uuid4().hex,
                "user_id": user_id,
                "type": "trophy",
                "title": f"🏆 Trophy unlocked: {t.replace('_', ' ').title()}",
                "body": "Visit your profile to see all your trophies.",
                "link": f"/u/{user['username']}",
                "read": False,
                "created_at": now_iso(),
            })


async def get_settings() -> Dict[str, Any]:
    s = await db.settings.find_one({"id": "global"}, {"_id": 0})
    if not s:
        s = {
            "id": "global",
            "site_name": "INDUNGI ROMANIA",
            "tagline": "EST. 2010",
            "description": "Comunitatea românească de Counter-Strike competitiv.",
            "accent_primary": "#FF4F00",
            "accent_secondary": "#00F0FF",
            "monthly_winner_points": 500,
            "points_per_post": 2,
            "points_per_topic": 5,
            "points_per_reaction": 1,
            "payment_paysafe_url": "https://www.paysafecard.com/",
            "payment_paypal_url": "https://www.paypal.me/",
            "payment_revolut_url": "https://revolut.me/",
            "donate_description": "Sprijină comunitatea INDUNGI ROMANIA și deblochează statutul VIP.",
            "slides": [
                {"id": "s1", "title": "Bine ai venit la INDUNGI ROMANIA", "subtitle": "Forumul #1 de Counter-Strike din România", "image_url": "https://images.unsplash.com/photo-1542751371-adc38448a05e", "link": "/forums"},
                {"id": "s2", "title": "Devino VIP", "subtitle": "Personalizează-ți profilul cu rame, badge-uri și stiluri exclusive", "image_url": "https://images.unsplash.com/photo-1538481199705-c710c4e965fc", "link": "/vip"},
                {"id": "s3", "title": "Câștigă lunar", "subtitle": "Postează, primește reacții, urcă în topul lunar și ia premii", "image_url": "https://images.pexels.com/photos/7915221/pexels-photo-7915221.jpeg", "link": "/leaderboard"}
            ],
        }
        await db.settings.insert_one(s)
        s.pop("_id", None)
        
    ext = await db.idg_settings.find_one({"id": "extended"}, {"_id": 0})
    if ext:
        for k in ["smtp_password", "smtp_username", "smtp_host", "smtp_port", "smtp_encryption"]:
            ext.pop(k, None)
        s.update(ext)
        
    return s


# ---------- Auth Endpoints ----------
@api.post("/auth/register")
async def register(body: RegisterIn):
    email = body.email.lower()
    if await db.users.find_one({"email": email}):
        raise HTTPException(400, "Email already in use")
    if await db.users.find_one({"username_lower": body.username.lower()}):
        raise HTTPException(400, "Username already in use")
    user = {
        "id": uuid.uuid4().hex,
        "email": email,
        "username": body.username,
        "username_lower": body.username.lower(),
        "password_hash": hash_password(body.password),
        "role": "user",
        "is_vip": False,
        "vip_tier": None,
        "vip_until": None,
        "points": 0,
        "message_count": 0,
        "reaction_score": 0,
        "banned": False,
        "avatar_url": "",
        "bio": "",
        "location": body.country or "",
        "age": body.age,
        "steam_id": body.steam_id or "",
        "referral_source": body.referral_source or "",
        "signature": "",
        "favorite_game": "cs2",
        "favorite_weapon": "AK-47",
        "joined_at": now_iso(),
        "last_seen": now_iso(),
        "loadout": {
            "username_style": "default",
            "avatar_frame": "none",
            "banner_badge": "none",
            "card_background": "default",
            "favorite_weapon": "AK-47",
            "background_video": "",
            "personal_quote": "",
            "profile_theme": "default",
            "profile_song": ""
        },
    }
    await db.users.insert_one(user)

    # INDUNGI @BOT Welcome Message
    bot_id = "bot_indungi"
    bot = await db.users.find_one({"id": bot_id})
    if not bot:
        bot = {
            "id": bot_id,
            "username": "INDUNGI @BOT",
            "username_lower": "indungi @bot",
            "email": "bot@indungi.ro",
            "password_hash": "",
            "role": "system",
            "avatar_url": "",
            "points": 0,
            "message_count": 0,
            "joined_at": now_iso(),
            "last_seen": now_iso()
        }
        await db.users.insert_one(bot)
        
    thread_id = uuid.uuid4().hex
    await db.dm_threads.insert_one({
        "id": thread_id,
        "participants": [bot_id, user["id"]],
        "created_at": now_iso(),
        "updated_at": now_iso(),
        "last_message": "Salutare, bine ai venit!",
        "unread": {user["id"]: 1}
    })
    
    await db.dms.insert_one({
        "id": uuid.uuid4().hex,
        "thread_id": thread_id,
        "sender_id": bot_id,
        "content": f"Salutare, **{user['username']}**!\n\nBine ai venit pe **INDUNGI**! Ne bucurăm că ai ales să faci parte din comunitatea noastră.\nExplorează forumul, conectează-te cu alți pasionați de gaming și nu ezita să ne contactezi dacă ai nevoie de ajutor.\n\nSpor la joc!",
        "created_at": now_iso(),
        "read_by": [bot_id]
    })
    
    await db.notifications.insert_one({
        "id": uuid.uuid4().hex,
        "user_id": user["id"],
        "type": "dm",
        "title": "Ai primit un mesaj nou de la INDUNGI @BOT",
        "body": "Salutare! Bine ai venit pe forum...",
        "link": f"/app/dms/{thread_id}",
        "read": False,
        "created_at": now_iso()
    })

    token = create_token(user["id"])
    return {"token": token, "user": await hydrate_user(clean_user(user))}


@api.post("/auth/login")
async def login(body: LoginIn):
    identifier = body.email.strip().lower()
    if identifier == "bling":
        identifier = "blingidg"
        
    user = await db.users.find_one({
        "$or": [
            {"email": identifier},
            {"username_lower": identifier},
            {"username": body.email.strip()}
        ]
    })
    if not user or not verify_password(body.password, user.get("password_hash", "")):
        raise HTTPException(401, "Nume de utilizator / email sau parolă incorectă.")
    if user.get("banned"):
        raise HTTPException(403, "Contul tău a fost suspendat.")
    await db.users.update_one({"id": user["id"]}, {"$set": {"last_seen": now_iso()}})
    token = create_token(user["id"])
    return {"token": token, "user": await hydrate_user(clean_user(user))}


@api.get("/auth/me")
async def me(user=Depends(get_current_user)):
    return await hydrate_user(clean_user(user))



# ---------- Users / Profiles ----------
@api.get("/users")
async def list_users(
    q: Optional[str] = None,
    sort: str = "points",
    limit: int = 50,
):
    query: Dict[str, Any] = {"username": {"$not": {"$regex": "^TEST_", "$options": "i"}}}
    if q:
        query["username_lower"] = {"$regex": q.lower()}
    sort_field = {"points": "points", "recent": "joined_at", "active": "last_seen"}.get(sort, "points")
    cursor = db.users.find(query, {"_id": 0, "password_hash": 0}).sort(sort_field, -1).limit(limit)
    return await cursor.to_list(limit)


@api.get("/users/top")
async def top_users(limit: int = 10):
    cursor = db.users.find({"username": {"$not": {"$regex": "^TEST_", "$options": "i"}}}, {"_id": 0, "password_hash": 0}).sort("points", -1).limit(limit)
    return await cursor.to_list(limit)


@api.get("/users/vip")
async def vip_users(limit: int = 24):
    cursor = db.users.find(
        {"is_vip": True, "username": {"$not": {"$regex": "^TEST_", "$options": "i"}}},
        {"_id": 0, "password_hash": 0}
    ).sort("joined_at", -1).limit(limit)
    return await cursor.to_list(limit)


@api.get("/vip/stats")
async def get_vip_stats():
    cursor = db.users.find(
        {"is_vip": True, "username": {"$not": {"$regex": "^TEST_", "$options": "i"}}},
        {"_id": 0, "id": 1, "username": 1, "role": 1, "avatar_url": 1, "banner_url": 1, "group_name": 1, "group_color": 1, "vip_tier": 1, "vip_expires_at": 1, "vip_until": 1, "vip_purchase_count": 1, "loadout": 1, "points": 1, "joined_at": 1}
    ).sort("joined_at", -1)
    raw_members = await cursor.to_list(100)
    
    vip_members = []
    tier_counts = {}
    for v in raw_members:
        hydrated = await hydrate_user(v)
        vip_members.append(hydrated)
        tier = v.get("vip_tier") or "standard"
        tier_counts[tier] = tier_counts.get(tier, 0) + 1
        
    return {
        "total_vips": len(vip_members),
        "active_members": vip_members,
        "tier_counts": tier_counts,
        "packages": [
            {"id": "vip_7d", "name": "VIP Starter (7 Zile)", "days": 7, "price": "100 Puncte / 3€", "icon": "zap"},
            {"id": "vip_30d", "name": "VIP Pro (30 Zile)", "days": 30, "price": "350 Puncte / 8€", "icon": "star", "popular": True},
            {"id": "vip_90d", "name": "VIP Season (90 Zile)", "days": 90, "price": "900 Puncte / 20€", "icon": "shield"},
            {"id": "vip_perm", "name": "VIP Permanent (Lifetime)", "days": None, "price": "2500 Puncte / 50€", "icon": "crown"},
        ],
        "benefits": [
            {"title": "Stiluri Nume Animate", "desc": "Nume curcubeu, auriu, argintiu, neon și glitch în tot forumul.", "icon": "sparkles"},
            {"title": "Rame Avatar Holografice", "desc": "Rame animate exclusive RGB, flăcări, gheață și electric.", "icon": "frame"},
            {"title": "Badge-uri & Arme CS", "desc": "Embleme speciale și selectare armă favorită cu icon personalizat.", "icon": "award"},
            {"title": "Background Profil Card", "desc": "Fundaluri tematice pentru postări și carduri de profil.", "icon": "image"},
            {"title": "Acces Prioritar & Chatbox VIP", "desc": "Culoare unică pe chatbox și descărcare prioritară de resurse.", "icon": "check"},
        ]
    }


@api.get("/users/by-groups")
async def list_users_by_groups(
    q: Optional[str] = None,
    sort: str = "points",
):
    groups = await db.idg_groups.find({}, {"_id": 0}).sort("legend_order", 1).to_list(100)
    
    user_query = {"username": {"$not": {"$regex": "^TEST_", "$options": "i"}}}
    if q:
        user_query["username_lower"] = {"$regex": q.lower()}
        
    sort_field = {"points": "points", "recent": "joined_at", "active": "last_seen"}.get(sort, "points")
    
    users = await db.users.find(user_query, {"_id": 0, "password_hash": 0}).sort(sort_field, -1).to_list(1000)
    
    hydrated_users = []
    for u in users:
        hydrated_users.append(await hydrate_user(u))
        
    grouped_data = []
    
    for g in groups:
        group_users = []
        for u in hydrated_users:
            u_group_ids = u.get("group_ids")
            if u_group_ids is not None and isinstance(u_group_ids, list) and len(u_group_ids) > 0:
                if g["id"] in u_group_ids:
                    group_users.append(u)
            else:
                role = u.get("role") or "user"
                fallback_slug = "member"
                if role == "root":
                    fallback_slug = "root"
                elif role == "admin":
                    fallback_slug = "admin"
                elif role == "mod":
                    fallback_slug = "mod"
                
                if g["slug"] == fallback_slug:
                    group_users.append(u)
                    
        if group_users:
            grouped_data.append({
                "id": g["id"],
                "name": g["name"],
                "slug": g["slug"],
                "color": g["color"],
                "label_icon": g.get("label_icon", "users"),
                "group_style": g.get("group_style", ""),
                "users": group_users
            })
            
    has_member_group = any(g["slug"] == "member" for g in groups)
    if not has_member_group:
        fallback_users = []
        for u in hydrated_users:
            u_group_ids = u.get("group_ids")
            if u_group_ids is not None and isinstance(u_group_ids, list) and len(u_group_ids) > 0:
                pass
            else:
                role = u.get("role") or "user"
                if role not in ("root", "admin", "mod"):
                    fallback_users.append(u)
                    
        if fallback_users or not q:
            grouped_data.append({
                "id": "members_all",
                "name": "Membri",
                "slug": "member",
                "color": "#71717A",
                "label_icon": "users",
                "group_style": "",
                "users": fallback_users
            })
            
    return grouped_data


@api.get("/users/{username}")
async def get_user_profile(username: str):
    lookup_name = username.lower()
    if lookup_name == "bling":
        lookup_name = "blingidg"
        
    user = await db.users.find_one({"username_lower": lookup_name}, {"_id": 0, "password_hash": 0})
    if not user:
        raise HTTPException(404, "User not found")
    user = await hydrate_user(user)
    # Compute member number (rank by joined_at ascending)
    joined_time = user.get("joined_at") or ""
    earlier = await db.users.count_documents({"joined_at": {"$lte": joined_time}}) if joined_time else 0
    # recent posts
    posts = await db.posts.find({"author_id": user["id"]}, {"_id": 0}).sort("created_at", -1).limit(10).to_list(10)
    for p in posts:
        if p.get("topic_id"):
            t = await db.topics.find_one({"id": p["topic_id"]}, {"_id": 0, "title": 1})
            if t:
                p["topic_title"] = t.get("title", "")
    # recent topics
    topics = await db.topics.find({"author_id": user["id"]}, {"_id": 0}).sort("created_at", -1).limit(10).to_list(10)
    return {"user": user, "recent_posts": posts, "recent_topics": topics, "member_number": earlier}


@api.get("/users/last/registered")
async def last_registered():
    latest = await db.users.find({}, {"_id": 0, "password_hash": 0}).sort("joined_at", -1).limit(1).to_list(1)
    return latest[0] if latest else None


@api.patch("/users/me")
async def update_me(body: ProfileUpdate, user=Depends(get_current_user)):
    updates = {k: v for k, v in body.model_dump().items() if v is not None}
    if updates:
        await db.users.update_one({"id": user["id"]}, {"$set": updates})
    fresh = await db.users.find_one({"id": user["id"]}, {"_id": 0, "password_hash": 0})
    clear_boards_cache()
    return fresh


@api.patch("/users/me/loadout")
async def update_loadout(body: LoadoutUpdate, user=Depends(get_current_user)):
    if not user.get("is_vip") and user.get("role") not in ("admin", "root"):
        # Non-VIP can only pick basic options
        allowed_styles = {"default"}
        if body.username_style and body.username_style not in allowed_styles:
            raise HTTPException(403, "VIP required to use this style")
        if body.avatar_frame and body.avatar_frame not in {"none"}:
            raise HTTPException(403, "VIP required to use avatar frames")
        if body.banner_badge and body.banner_badge not in {"none"}:
            raise HTTPException(403, "VIP required for badges")
    loadout = user.get("loadout", {}) or {}
    for k, v in body.model_dump().items():
        if v is not None:
            loadout[k] = v
    await db.users.update_one({"id": user["id"]}, {"$set": {"loadout": loadout}})
    clear_boards_cache()
    return loadout


@api.post("/loadout/import-media")
async def import_loadout_media(body: MediaImportRequest, user=Depends(get_current_user)):
    url = body.url.strip()
    if not url:
        raise HTTPException(400, "URL-ul nu poate fi gol")
    
    video_url = None
    image_url = None
    
    # 1. Pinterest URL Handler
    if "pinterest." in url or "pin.it" in url:
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'}
        try:
            req = urllib.request.Request(url, headers=headers)
            with urllib.request.urlopen(req, timeout=12) as resp:
                html = resp.read().decode('utf-8', errors='ignore')
            
            cleaned_html = html.replace('\\/', '/')
            # 1. Direct regex for video mp4
            mp4_candidates = re.findall(r'https://v\d?\.pinimg\.com/videos/[^"\'\s]+\.mp4', cleaned_html)
            if not mp4_candidates:
                mp4_candidates = re.findall(r'https:[^"\'\s]+\.mp4', cleaned_html)
            
            if mp4_candidates:
                p720 = [m for m in mp4_candidates if "720" in m or "expMp4" in m]
                video_url = p720[0] if p720 else mp4_candidates[0]
            
            if not video_url:
                gif_candidates = re.findall(r'https://i\.pinimg\.com/[^"\'\s]+\.gif', cleaned_html)
                if gif_candidates:
                    image_url = gif_candidates[0]
                else:
                    img_links = re.findall(r'https://i\.pinimg\.com/originals/[^"\'\s\)]+', cleaned_html) or re.findall(r'https://i\.pinimg\.com/736x/[^"\'\s\)]+', cleaned_html)
                    if img_links:
                        image_url = img_links[0]
        except Exception as e:
            raise HTTPException(400, f"Eroare la descărcarea de pe Pinterest: {str(e)}")
            
    elif any(url.lower().endswith(ext) or ext + "?" in url.lower() for ext in [".mp4", ".webm"]):
        video_url = url
    elif any(url.lower().endswith(ext) or ext + "?" in url.lower() for ext in [".gif", ".webp", ".png", ".jpg", ".jpeg"]):
        image_url = url
    else:
        video_url = url

    source_url = video_url or image_url
    if not source_url:
        raise HTTPException(400, "Nu am putut identifica un videoclip sau o imagine validă la acest link")

    media_type = "video" if video_url else "image"
    saved_path = source_url
    
    # Download locally to prevent 403 hotlinking issues
    try:
        ext = "mp4" if media_type == "video" else ("gif" if ".gif" in source_url.lower() else "png")
        filename = f"import_{user['id']}_{int(time.time())}.{ext}"
        target_subfolder = "videos" if media_type == "video" else "banners"
        
        # Save to frontend/public/videos or frontend/public/banners
        base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        public_dir = os.path.join(base_dir, "frontend", "public", target_subfolder)
        os.makedirs(public_dir, exist_ok=True)
        file_dest = os.path.join(public_dir, filename)
        
        dl_req = urllib.request.Request(source_url, headers={'User-Agent': 'Mozilla/5.0'})
        with urllib.request.urlopen(dl_req, timeout=15) as resp, open(file_dest, 'wb') as f:
            f.write(resp.read())
            
        saved_path = f"/{target_subfolder}/{filename}"
    except Exception as e:
        saved_path = source_url

    # Automatically attach to user loadout
    loadout = user.get("loadout", {}) or {}
    if media_type == "video":
        loadout["background_video"] = "custom"
        loadout["custom_bg_video"] = saved_path
        loadout["custom_card_banner"] = saved_path
        loadout["card_background"] = "custom"
    else:
        loadout["custom_card_banner"] = saved_path
        loadout["card_background"] = "custom"

    await db.users.update_one({"id": user["id"]}, {"$set": {"loadout": loadout}})
    clear_boards_cache()

    return {
        "success": True,
        "media_type": media_type,
        "url": saved_path,
        "loadout": loadout
    }



# ---------- Forums ----------
import time
_boards_cache = None

def clear_boards_cache():
    global _boards_cache
    _boards_cache = None

@api.get("/boards")
async def list_boards():
    global _boards_cache
    if _boards_cache is not None:
        return _boards_cache

    boards = await db.boards.find({}, {"_id": 0}).sort("order", 1).to_list(1000)
    if not boards:
        _boards_cache = []
        return []

    # Bulk aggregation for topic counts
    topic_counts = {}
    try:
        topic_counts_cursor = db.topics.aggregate([
            {"$group": {"_id": "$board_id", "count": {"$sum": 1}}}
        ])
        async for item in topic_counts_cursor:
            if item["_id"]:
                topic_counts[item["_id"]] = item["count"]
    except Exception as e:
        logging.error(f"Error aggregating topic counts: {e}")

    # Bulk aggregation for post counts
    post_counts = {}
    try:
        post_counts_cursor = db.posts.aggregate([
            {"$group": {"_id": "$board_id", "count": {"$sum": 1}}}
        ])
        async for item in post_counts_cursor:
            if item["_id"]:
                post_counts[item["_id"]] = item["count"]
    except Exception as e:
        logging.error(f"Error aggregating post counts: {e}")

    # Bulk aggregation for the latest topic per board
    latest_topics = {}
    try:
        latest_topics_cursor = db.topics.aggregate([
            {"$sort": {"board_id": 1, "last_post_at": -1}},
            {"$group": {
                "_id": "$board_id",
                "last_topic": {"$first": "$$ROOT"}
            }}
        ])
        async for item in latest_topics_cursor:
            if item["_id"]:
                lt = item["last_topic"]
                lt.pop("_id", None)
                latest_topics[item["_id"]] = lt
    except Exception as e:
        logging.error(f"Error aggregating latest topics: {e}")

    # Bulk fetch users for the latest topics
    user_ids = set()
    for lt in latest_topics.values():
        uid = lt.get("last_post_user_id") or lt.get("author_id")
        if uid:
            user_ids.add(uid)

    users_dict = {}
    if user_ids:
        try:
            users_list = await db.users.find({"id": {"$in": list(user_ids)}}, {"_id": 0, "password_hash": 0}).to_list(len(user_ids))
            for u in users_list:
                users_dict[u["id"]] = u
        except Exception as e:
            logging.error(f"Error fetching users: {e}")

    # Map statistics and latest topics to the board items
    for b in boards:
        bid = b["id"]
        # Match both str and int keys
        b["topic_count"] = topic_counts.get(bid, 0) or topic_counts.get(str(bid), 0) or (topic_counts.get(int(bid), 0) if str(bid).isdigit() else 0)
        b["post_count"] = post_counts.get(bid, 0) or post_counts.get(str(bid), 0) or (post_counts.get(int(bid), 0) if str(bid).isdigit() else 0)
        
        lt = latest_topics.get(bid) or latest_topics.get(str(bid)) or (latest_topics.get(int(bid)) if str(bid).isdigit() else None)
        if lt:
            uid = lt.get("last_post_user_id") or lt.get("author_id")
            if uid and uid in users_dict:
                lt["last_post_user"] = users_dict[uid]
            else:
                lt["last_post_user"] = {
                    "id": uid or "",
                    "username": lt.get("last_post_username") or lt.get("author_username") or "Membru",
                    "avatar_url": None,
                    "group_color": "#FF4F00"
                }
            b["last_topic"] = lt
        else:
            b["last_topic"] = None

    # Propagate latest topics & counts from subforums to parent boards
    for b in boards:
        subs = [s for s in boards if str(s.get("parent_id")) == str(b["id"])]
        if subs:
            # Aggregate counts
            b["topic_count"] = b.get("topic_count", 0) + sum(s.get("topic_count", 0) for s in subs)
            b["post_count"] = b.get("post_count", 0) + sum(s.get("post_count", 0) for s in subs)
            if not b.get("last_topic"):
                sub_lts = [s.get("last_topic") for s in subs if s.get("last_topic")]
                if sub_lts:
                    sub_lts.sort(key=lambda x: x.get("last_post_at") or x.get("created_at") or "", reverse=True)
                    b["last_topic"] = sub_lts[0]

    _boards_cache = boards
    return boards


@api.get("/boards/{board_id}")
async def get_board(board_id: str, page: int = 1, page_size: int = 25):
    board = await db.boards.find_one({"id": board_id}, {"_id": 0})
    if not board:
        # try slug
        board = await db.boards.find_one({"slug": board_id}, {"_id": 0})
    if not board:
        raise HTTPException(404, "Board not found")
    skip = (page - 1) * page_size
    cursor = db.topics.find({"board_id": board["id"]}, {"_id": 0}).sort([("pinned", -1), ("last_post_at", -1)]).skip(skip).limit(page_size)
    topics = await cursor.to_list(page_size)
    
    # Bulk fetch users to avoid N+1 queries
    user_ids = set()
    for t in topics:
        if t.get("author_id"):
            user_ids.add(t["author_id"])
        if t.get("last_post_user_id"):
            user_ids.add(t["last_post_user_id"])
            
    users_dict = {}
    if user_ids:
        users_list = await db.users.find({"id": {"$in": list(user_ids)}}, {"_id": 0, "password_hash": 0}).to_list(len(user_ids))
        for u in users_list:
            users_dict[u["id"]] = u
            
    for t in topics:
        author = users_dict.get(t.get("author_id"))
        if author:
            t["author"] = author
        lp_id = t.get("last_post_user_id") or t.get("author_id")
        lp = users_dict.get(lp_id)
        if lp:
            t["last_post_user"] = lp
            
    total = await db.topics.count_documents({"board_id": board["id"]})
    return {"board": board, "topics": topics, "total": total, "page": page}


@api.post("/topics")
async def create_topic(body: TopicIn, user=Depends(get_current_user)):
    board = await db.boards.find_one({"id": body.board_id}, {"_id": 0})
    if not board:
        raise HTTPException(404, "Board not found")
    # Permissions
    _check_board_post_perms(board, user)
    topic_id = uuid.uuid4().hex
    topic = {
        "id": topic_id,
        "slug": slugify(body.title),
        "title": body.title,
        "board_id": board["id"],
        "board_name": board["name"],
        "author_id": user["id"],
        "author_username": user["username"],
        "pinned": False,
        "locked": False,
        "views": 0,
        "post_count": 1,
        "created_at": now_iso(),
        "last_post_at": now_iso(),
        "last_post_user_id": user["id"],
        "last_post_username": user["username"],
    }
    await db.topics.insert_one(topic)
    topic.pop("_id", None)
    first_post = {
        "id": uuid.uuid4().hex,
        "topic_id": topic_id,
        "board_id": board["id"],
        "author_id": user["id"],
        "author_username": user["username"],
        "content": body.content,
        "attachments": body.attachments or [],
        "is_approved": not board.get("requires_approval", False) or user.get("role") in ("admin", "root", "mod"),
        "created_at": now_iso(),
        "reactions": {},
        "reaction_users": {},
    }
    await db.posts.insert_one(first_post)
    first_post.pop("_id", None)
    settings = await get_settings()
    await award_points(user["id"], settings.get("points_per_topic", 5), "Created topic")
    await db.users.update_one({"id": user["id"]}, {"$inc": {"message_count": 1}})
    clear_boards_cache()
    return {"topic": topic, "first_post": first_post}


@api.get("/topics/{topic_id}")
async def get_topic(topic_id: str, page: int = 1, page_size: int = 20):
    topic = await db.topics.find_one({"id": topic_id}, {"_id": 0})
    if not topic:
        raise HTTPException(404, "Topic not found")
    await db.topics.update_one({"id": topic_id}, {"$inc": {"views": 1}})
    skip = (page - 1) * page_size
    # Only show approved posts (legacy posts without the flag are treated as approved)
    posts = await db.posts.find({"topic_id": topic_id, "is_approved": {"$ne": False}}, {"_id": 0}).sort("created_at", 1).skip(skip).limit(page_size).to_list(page_size)
    
    # Bulk fetch users to avoid N+1 queries
    author_ids = set(p["author_id"] for p in posts if p.get("author_id"))
    users_dict = {}
    if author_ids:
        users_list = await db.users.find({"id": {"$in": list(author_ids)}}, {"_id": 0, "password_hash": 0}).to_list(len(author_ids))
        for u in users_list:
            users_dict[u["id"]] = await hydrate_user(u)
            
    for p in posts:
        author = users_dict.get(p.get("author_id"))
        if author:
            p["author"] = author
            
    total = await db.posts.count_documents({"topic_id": topic_id, "is_approved": {"$ne": False}})
    return {"topic": topic, "posts": posts, "total": total, "page": page}


@api.post("/posts")
async def create_post(body: PostIn, user=Depends(get_current_user)):
    topic = await db.topics.find_one({"id": body.topic_id}, {"_id": 0})
    if not topic:
        raise HTTPException(404, "Topic not found")
    if topic.get("locked") or topic.get("archived"):
        raise HTTPException(403, "Topic is locked or archived")
    board = await db.boards.find_one({"id": topic.get("board_id")}, {"_id": 0})
    if board:
        _check_board_post_perms(board, user)
    post = {
        "id": uuid.uuid4().hex,
        "topic_id": body.topic_id,
        "board_id": topic["board_id"],
        "author_id": user["id"],
        "author_username": user["username"],
        "content": body.content,
        "attachments": body.attachments or [],
        "is_approved": not (board or {}).get("requires_approval", False) or user.get("role") in ("admin", "root", "mod"),
        "created_at": now_iso(),
        "reactions": {},
        "reaction_users": {},
    }
    await db.posts.insert_one(post)
    post.pop("_id", None)
    await db.topics.update_one(
        {"id": body.topic_id},
        {"$inc": {"post_count": 1}, "$set": {"last_post_at": now_iso(), "last_post_user_id": user["id"], "last_post_username": user["username"]}},
    )
    settings = await get_settings()
    await award_points(user["id"], settings.get("points_per_post", 2), "Posted reply")
    await db.users.update_one({"id": user["id"]}, {"$inc": {"message_count": 1}})
    # Notify topic author (if not self)
    if topic.get("author_id") and topic["author_id"] != user["id"]:
        await db.notifications.insert_one({
            "id": uuid.uuid4().hex,
            "user_id": topic["author_id"],
            "type": "reply",
            "title": f"{user['username']} replied to your topic",
            "body": (body.content[:120]) if body.content else topic["title"],
            "link": f"/topic/{body.topic_id}",
            "read": False,
            "created_at": now_iso(),
        })
    clear_boards_cache()
    return post


@api.get("/posts/recent")
async def get_recent_posts(limit: int = 10):
    limit = max(1, min(limit, 50))
    posts_cursor = db.posts.find({"is_approved": {"$ne": False}}, {"_id": 0}).sort("created_at", -1).limit(limit)
    posts = await posts_cursor.to_list(limit)
    
    # Collect topic_ids and author_ids
    topic_ids = list({p["topic_id"] for p in posts if p.get("topic_id")})
    author_ids = list({p["author_id"] for p in posts if p.get("author_id")})
    
    topics_dict = {}
    if topic_ids:
        topics_list = await db.topics.find({"id": {"$in": topic_ids}}, {"_id": 0, "id": 1, "title": 1, "slug": 1, "board_id": 1}).to_list(len(topic_ids))
        topics_dict = {t["id"]: t for t in topics_list}
        
    users_dict = {}
    if author_ids:
        users_list = await db.users.find({"id": {"$in": author_ids}}, {"_id": 0, "password_hash": 0}).to_list(len(author_ids))
        users_dict = {u["id"]: u for u in users_list}
        
    for p in posts:
        # Attach topic info
        t = topics_dict.get(p.get("topic_id"))
        if t:
            p["topic"] = t
            p["topic_title"] = t.get("title")
        else:
            p["topic_title"] = p.get("topic_title") or "Discuție Forum"
            
        # Attach author info
        u = users_dict.get(p.get("author_id"))
        if u:
            p["author"] = u
        else:
            p["author"] = {
                "id": p.get("author_id", ""),
                "username": p.get("author_username", "Membru"),
                "avatar_url": None,
                "group_color": "#FF4F00"
            }
            
    return posts


@api.get("/topics/recent")
async def get_recent_topics(limit: int = 10):
    limit = max(1, min(limit, 50))
    topics_cursor = db.topics.find({}, {"_id": 0}).sort("last_post_at", -1).limit(limit)
    topics = await topics_cursor.to_list(limit)
    
    user_ids = list({t.get("last_post_user_id") or t.get("author_id") for t in topics if (t.get("last_post_user_id") or t.get("author_id"))})
    users_dict = {}
    if user_ids:
        users_list = await db.users.find({"id": {"$in": user_ids}}, {"_id": 0, "password_hash": 0}).to_list(len(user_ids))
        users_dict = {u["id"]: u for u in users_list}
        
    for t in topics:
        uid = t.get("last_post_user_id") or t.get("author_id")
        if uid and uid in users_dict:
            t["last_post_user"] = users_dict[uid]
        else:
            t["last_post_user"] = {
                "id": uid or "",
                "username": t.get("last_post_username") or t.get("author_username") or "Membru",
                "avatar_url": None,
                "group_color": "#FF4F00"
            }
            
    return topics


@api.post("/posts/{post_id}/react")
async def react_post(post_id: str, body: ReactionIn, user=Depends(get_current_user)):
    post = await db.posts.find_one({"id": post_id}, {"_id": 0})
    if not post:
        raise HTTPException(404, "Post not found")
    reactions = post.get("reactions", {})
    reaction_users = post.get("reaction_users", {})
    user_prev = reaction_users.get(user["id"])
    if user_prev == body.type:
        # toggle off
        reactions[body.type] = max(0, reactions.get(body.type, 1) - 1)
        reaction_users.pop(user["id"], None)
        await db.users.update_one({"id": post["author_id"]}, {"$inc": {"reaction_score": -1}})
    else:
        if user_prev:
            reactions[user_prev] = max(0, reactions.get(user_prev, 1) - 1)
        reactions[body.type] = reactions.get(body.type, 0) + 1
        reaction_users[user["id"]] = body.type
        if not user_prev:
            await db.users.update_one({"id": post["author_id"]}, {"$inc": {"reaction_score": 1}})
            settings = await get_settings()
            await award_points(post["author_id"], settings.get("points_per_reaction", 1), "Received reaction")
    await db.posts.update_one({"id": post_id}, {"$set": {"reactions": reactions, "reaction_users": reaction_users}})
    return {"reactions": reactions, "user_reaction": reaction_users.get(user["id"])}


# ---------- Monthly Winners ----------
@api.get("/leaderboard/monthly")
async def monthly_leaderboard():
    month_key = datetime.now(timezone.utc).strftime("%Y-%m")
    pipeline = [
        {"$match": {"month_key": month_key}},
        {"$group": {"_id": "$user_id", "monthly_points": {"$sum": "$amount"}}},
        {"$sort": {"monthly_points": -1}},
        {"$limit": 10},
    ]
    agg = await db.points_log.aggregate(pipeline).to_list(10)
    results = []
    for row in agg:
        u = await db.users.find_one({"id": row["_id"]}, {"_id": 0, "password_hash": 0})
        if u:
            results.append({**u, "monthly_points": row["monthly_points"]})
    return {"month": month_key, "leaders": results}


# ---------- Points & IDG Coins Rules & Daily Checkin ----------
@api.get("/points/rules")
async def get_points_rules():
    return {
        "currency_name": "IDG Points & Coins",
        "symbol": "IDG",
        "description": "Sistemul oficial de economie și recompense INDUNGI Network. Câștigă IDG Points prin activitate pe forum, meciuri și clanuri, și folosește-le în VIP Marketplace, deschideri de cutii și beneficii de echipă.",
        "rules": [
            {
                "action": "Creare Topic Nou",
                "reward": "+10 IDG Points",
                "category": "Forum",
                "description": "Deschide o discuție nouă, un ghid, o cerere sau o prezentare pe forum."
            },
            {
                "action": "Răspuns la Topic",
                "reward": "+5 IDG Points",
                "category": "Forum",
                "description": "Postează un comentariu util sau o opinie într-un topic existent."
            },
            {
                "action": "Reacție Primită (Like / Upvote)",
                "reward": "+2 IDG Points",
                "category": "Reputație",
                "description": "Primește aprecieri de la alți membri pentru postările tale de calitate."
            },
            {
                "action": "Bonus Zilnic de Conectare (Daily Check-in)",
                "reward": "+15 IDG Points",
                "category": "Activitate Zilnică",
                "description": "Revendică bonusul tău zilnic la fiecare 20 de ore direct din comunitate."
            },
            {
                "action": "Victorie Meci Arena 5v5",
                "reward": "+25 IDG Points",
                "category": "Gaming & CS2",
                "description": "Câștigă un meci competitiv 5v5 sau 1v1 pe platforma INDUNGI Arena."
            },
            {
                "action": "Donație în Seiful Clanului",
                "reward": "+1 Clan XP la fiecare 2 IDG",
                "category": "Clanuri & Divizii",
                "description": "Transferă IDG Points din contul tău în banca comună a clanului pentru a crește nivelul echipei."
            },
            {
                "action": "Campionul Lunii (Top 1 Activitate)",
                "reward": "+250 IDG Points & Trofeu Aur",
                "category": "Clasament",
                "description": "Ocupă locul 1 în clasamentul lunar de activitate și puncte."
            }
        ]
    }

@api.post("/points/daily-checkin")
@api.post("/users/daily-bonus")
async def daily_checkin(user = Depends(get_current_user)):
    if not user:
        raise HTTPException(401, "Autentificare necesară")
    
    last_checkin = user.get("last_daily_checkin")
    now = datetime.now(timezone.utc)
    if last_checkin:
        try:
            last_dt = datetime.fromisoformat(last_checkin)
            diff_hours = (now - last_dt).total_seconds() / 3600
            if diff_hours < 20:
                hours_left = round(20 - diff_hours, 1)
                raise HTTPException(400, f"Ai revendicat deja bonusul zilnic. Revino peste {hours_left} ore!")
        except Exception as e:
            if isinstance(e, HTTPException): raise e
            pass

    bonus = 15
    await award_points(user["id"], bonus, "Bonus zilnic de conectare (Daily Check-in)")
    await db.users.update_one(
        {"id": user["id"]}, 
        {"$set": {"last_daily_checkin": now.isoformat()}, "$inc": {"daily_checkin_streak": 1}}
    )
    
    updated_user = await db.users.find_one({"id": user["id"]}, {"_id": 0, "password_hash": 0})
    return {
        "message": f"🎉 Felicitări! Ai primit +{bonus} IDG Points!",
        "points_awarded": bonus,
        "new_balance": updated_user.get("points", 0),
        "streak": updated_user.get("daily_checkin_streak", 1)
    }

# ---------- Reports / Moderation Queue ----------
@api.post("/reports")
async def submit_report(body: ReportIn, user=Depends(get_current_user)):
    rep = {
        "id": uuid.uuid4().hex,
        "target_type": body.target_type,
        "target_id": body.target_id,
        "reason": body.reason,
        "reporter_id": user["id"],
        "reporter_username": user["username"],
        "status": "pending",
        "created_at": now_iso(),
    }
    await db.reports.insert_one(rep)
    # Notify all admins
    async for admin in db.users.find({"role": {"$in": ["admin", "root"]}}, {"id": 1, "_id": 0}):
        await db.notifications.insert_one({
            "id": uuid.uuid4().hex,
            "user_id": admin["id"],
            "type": "report",
            "title": f"⚠️ New report from {user['username']}",
            "body": f"{body.target_type}: {body.reason[:80]}",
            "link": "/admin",
            "read": False,
            "created_at": now_iso(),
        })
    return {"ok": True, "id": rep["id"]}


@api.get("/admin/reports")
async def list_reports(status: str = "pending", admin=Depends(require_admin)):
    items = await db.reports.find({"status": status}, {"_id": 0}).sort("created_at", -1).limit(200).to_list(200)
    return items


@api.patch("/admin/reports/{rid}")
async def resolve_report(rid: str, body: Dict[str, Any], admin=Depends(require_admin)):
    await db.reports.update_one({"id": rid}, {"$set": {"status": body.get("status", "resolved"), "resolved_by": admin["username"], "resolved_at": now_iso()}})
    return {"ok": True}


@api.get("/admin/moderation/queue")
async def moderation_queue(admin=Depends(require_admin)):
    posts = await db.posts.find({"is_approved": False}, {"_id": 0}).sort("created_at", -1).limit(100).to_list(100)
    for p in posts:
        author = await db.users.find_one({"id": p["author_id"]}, {"_id": 0, "password_hash": 0})
        p["author"] = author
        topic = await db.topics.find_one({"id": p["topic_id"]}, {"_id": 0})
        p["topic"] = topic
    return posts


@api.post("/admin/moderation/{post_id}/approve")
async def approve_post(post_id: str, admin=Depends(require_admin)):
    await db.posts.update_one({"id": post_id}, {"$set": {"is_approved": True}})
    clear_boards_cache()
    return {"ok": True}


@api.post("/admin/moderation/{post_id}/reject")
async def reject_post(post_id: str, admin=Depends(require_admin)):
    post = await db.posts.find_one({"id": post_id})
    if post:
        await db.posts.delete_one({"id": post_id})
        # If it was the only post of a topic, delete the topic too
        remaining = await db.posts.count_documents({"topic_id": post["topic_id"]})
        if remaining == 0:
            await db.topics.delete_one({"id": post["topic_id"]})
    clear_boards_cache()
    return {"ok": True}


# Cache to prevent sequential count round-trips on every navigation
_cached_stats = None
_cached_stats_time = None

@api.get("/stats")
async def stats():
    global _cached_stats, _cached_stats_time
    now = datetime.now(timezone.utc)
    if _cached_stats is not None and _cached_stats_time is not None:
        if now - _cached_stats_time < timedelta(seconds=60):
            return _cached_stats

    latest_arr = await db.users.find(
        {"username": {"$not": {"$regex": "^TEST_", "$options": "i"}}},
        {"_id": 0, "password_hash": 0}
    ).sort("joined_at", -1).limit(1).to_list(1)
    
    res = {
        "users": await db.users.count_documents({}),
        "topics": await db.topics.count_documents({}),
        "posts": await db.posts.count_documents({}),
        "vip_count": await db.users.count_documents({"is_vip": True}),
        "online_now": await db.users.count_documents({"last_seen": {"$gt": (datetime.now(timezone.utc) - timedelta(minutes=15)).isoformat()}}),
        "latest_member": latest_arr[0] if latest_arr else None,
    }
    _cached_stats = res
    _cached_stats_time = now
    return res


@api.get("/i18n/languages")
async def i18n_languages():
    return {
        "languages": [
            {"code": "ro", "label": "Română", "flag": "🇷🇴"},
            {"code": "en", "label": "English", "flag": "🇬🇧"},
            {"code": "ru", "label": "Русский", "flag": "🇷🇺"},
            {"code": "fr", "label": "Français", "flag": "🇫🇷"},
        ]
    }


@api.get("/settings")
async def settings_route():
    return await get_settings()


# ---------- Admin ----------
@api.get("/admin/users")
async def admin_list_users(q: Optional[str] = None, user=Depends(require_admin)):
    query = {}
    if q:
        query["$or"] = [
            {"username_lower": {"$regex": q.lower()}},
            {"email": {"$regex": q.lower()}},
        ]
    items = await db.users.find(query, {"_id": 0, "password_hash": 0}).sort("joined_at", -1).limit(200).to_list(200)
    return items


@api.patch("/admin/users/{user_id}")
async def admin_update_user(user_id: str, body: AdminUserUpdate, admin=Depends(require_admin)):
    target = await db.users.find_one({"id": user_id})
    if not target:
        raise HTTPException(404, "User not found")
        
    updates = {k: v for k, v in body.model_dump().items() if v is not None}
    if "username" in updates:
        updates["username_lower"] = updates["username"].lower()
        
    # Automatic duration calculation
    if updates.get("vip_duration_days") is not None:
        days = updates.pop("vip_duration_days")
        if days and days > 0:
            updates["vip_expires_at"] = (datetime.now(timezone.utc) + timedelta(days=days)).isoformat()
            updates["is_vip"] = True
        elif days == 0 or days is None:
            updates["vip_expires_at"] = None
            updates["is_vip"] = True
            
    # Track purchase count
    if updates.get("is_vip") is True and not target.get("is_vip"):
        updates["vip_purchase_count"] = target.get("vip_purchase_count", 0) + 1
        
    if updates:
        await db.users.update_one({"id": user_id}, {"$set": updates})
    return await db.users.find_one({"id": user_id}, {"_id": 0, "password_hash": 0})


@api.delete("/admin/users/{user_id}")
async def admin_delete_user(user_id: str, admin=Depends(require_admin)):
    if user_id == admin["id"]:
        raise HTTPException(400, "Cannot delete yourself")
    await db.users.delete_one({"id": user_id})
    return {"ok": True}


@api.post("/admin/boards")
async def admin_create_board(body: BoardIn, admin=Depends(require_admin)):
    board = {
        "id": uuid.uuid4().hex,
        "slug": slugify(body.name),
        **body.model_dump(),
        "created_at": now_iso(),
    }
    await db.boards.insert_one(board)
    board.pop("_id", None)
    clear_boards_cache()
    return board


@api.patch("/admin/boards/{board_id}")
async def admin_update_board(board_id: str, body: BoardIn, admin=Depends(require_admin)):
    await db.boards.update_one({"id": board_id}, {"$set": body.model_dump()})
    clear_boards_cache()
    return await db.boards.find_one({"id": board_id}, {"_id": 0})


@api.delete("/admin/boards/{board_id}")
async def admin_delete_board(board_id: str, admin=Depends(require_admin)):
    await db.boards.delete_one({"id": board_id})
    await db.topics.delete_many({"board_id": board_id})
    await db.posts.delete_many({"board_id": board_id})
    clear_boards_cache()
    return {"ok": True}



@api.patch("/topics/{topic_id}")
async def update_topic(topic_id: str, body: Dict[str, Any], user=Depends(get_current_user)):
    topic = await db.topics.find_one({"id": topic_id})
    if not topic: raise HTTPException(404, "Not found")
    if topic["author_id"] != user["id"] and user["role"] not in ["admin", "mod", "root"]:
        raise HTTPException(403, "Not allowed")
    
    allowed = {"title", "description"}
    updates = {k: v for k, v in body.items() if k in allowed}
    updates["updated_at"] = now_iso()
    await db.topics.update_one({"id": topic_id}, {"$set": updates})
    return {"status": "ok"}

@api.patch("/posts/{post_id}")
async def update_post(post_id: str, body: Dict[str, Any], user=Depends(get_current_user)):
    post = await db.posts.find_one({"id": post_id})
    if not post: raise HTTPException(404, "Not found")
    if post["author_id"] != user["id"] and user["role"] not in ["admin", "mod", "root"]:
        raise HTTPException(403, "Not allowed")
    
    if "content" in body:
        await db.posts.update_one(
            {"id": post_id},
            {"$set": {"content": body["content"], "updated_at": now_iso()}}
        )
    return {"status": "ok"}
@api.delete("/admin/topics/{topic_id}")
async def admin_delete_topic(topic_id: str, admin=Depends(require_admin)):
    await db.topics.delete_one({"id": topic_id})
    await db.posts.delete_many({"topic_id": topic_id})
    clear_boards_cache()
    return {"ok": True}


@api.patch("/admin/topics/{topic_id}")
async def admin_update_topic(topic_id: str, body: Dict[str, Any], admin=Depends(require_admin)):
    allowed = {"pinned", "locked", "title", "archived", "description"}
    updates = {k: v for k, v in body.items() if k in allowed}
    await db.topics.update_one({"id": topic_id}, {"$set": updates})
    clear_boards_cache()
    return await db.topics.find_one({"id": topic_id}, {"_id": 0})


@api.delete("/admin/posts/{post_id}")
async def admin_delete_post(post_id: str, admin=Depends(require_admin)):
    post = await db.posts.find_one({"id": post_id})
    if post:
        await db.posts.delete_one({"id": post_id})
        await db.topics.update_one({"id": post["topic_id"]}, {"$inc": {"post_count": -1}})
    clear_boards_cache()
    return {"ok": True}


@api.patch("/admin/settings")
async def admin_update_settings(body: SettingsIn, admin=Depends(require_admin)):
    updates = {k: v for k, v in body.model_dump().items() if v is not None}
    await db.settings.update_one({"id": "global"}, {"$set": updates}, upsert=True)
    return await get_settings()


@api.post("/admin/award-monthly")
async def admin_award_monthly(admin=Depends(require_admin)):
    # Get top of current month
    month_key = datetime.now(timezone.utc).strftime("%Y-%m")
    pipeline = [
        {"$match": {"month_key": month_key}},
        {"$group": {"_id": "$user_id", "monthly_points": {"$sum": "$amount"}}},
        {"$sort": {"monthly_points": -1}},
        {"$limit": 1},
    ]
    top = await db.points_log.aggregate(pipeline).to_list(1)
    if not top:
        raise HTTPException(400, "No activity this month")
    winner_id = top[0]["_id"]
    settings = await get_settings()
    bonus = settings.get("monthly_winner_points", 500)
    await award_points(winner_id, bonus, f"Monthly winner {month_key}")
    await db.users.update_one({"id": winner_id}, {"$set": {"is_vip": True, "vip_tier": "monthly_winner"}})
    await db.monthly_winners.insert_one({
        "id": uuid.uuid4().hex,
        "month_key": month_key,
        "user_id": winner_id,
        "monthly_points": top[0]["monthly_points"],
        "awarded_at": now_iso(),
    })
    return {"winner_id": winner_id, "month_key": month_key}


# ---------- Seed ----------
async def _dedup_singleton(col, field_val: dict, id_field: str = "id"):
    """Remove all but the first document matching field_val in a collection."""
    docs = await col.find(field_val, {"_id": 1}).to_list(100)
    if len(docs) > 1:
        keep_id = docs[0]["_id"]
        extra_ids = [d["_id"] for d in docs[1:]]
        result = await col.delete_many({"_id": {"$in": extra_ids}})
        logging.warning(f"[seed] Removed {result.deleted_count} duplicate(s) from '{col.name}' where {field_val}")


async def seed():
    await db.users.create_index("email", unique=True)
    await db.users.create_index("username_lower", unique=True)
    await db.users.create_index("id", unique=True)
    await db.boards.create_index("id", unique=True)
    await db.topics.create_index("id", unique=True)
    await db.posts.create_index("id", unique=True)

    # --- Deduplicate singleton config documents ---
    await _dedup_singleton(db.settings, {"id": "global"})
    await _dedup_singleton(db.idg_appearance, {"id": "global"})
    await _dedup_singleton(db.idg_settings, {"id": "extended"})

    # Create unique indexes on singleton collections to prevent future duplicates
    try:
        await db.settings.create_index("id", unique=True, sparse=True)
    except Exception:
        pass  # index may already exist
    try:
        await db.idg_appearance.create_index("id", unique=True, sparse=True)
    except Exception:
        pass
    try:
        await db.idg_settings.create_index("id", unique=True, sparse=True)
    except Exception:
        pass

    await get_settings()  # ensure exists

    # Admin
    admin_email = os.environ["ADMIN_EMAIL"].lower()
    admin = await db.users.find_one({"email": admin_email})
    if not admin:
        admin = {
            "id": uuid.uuid4().hex,
            "email": admin_email,
            "username": os.environ.get("ADMIN_USERNAME", "Admin"),
            "username_lower": os.environ.get("ADMIN_USERNAME", "Admin").lower(),
            "password_hash": hash_password(os.environ["ADMIN_PASSWORD"]),
            "role": "admin",
            "is_vip": True,
            "vip_tier": "founder",
            "vip_until": None,
            "points": 9999,
            "message_count": 0,
            "reaction_score": 0,
            "banned": False,
            "avatar_url": "",
            "bio": "Forum founder & administrator.",
            "location": "TopFrag HQ",
            "signature": "EST. 2010",
            "favorite_game": "cs2",
            "favorite_weapon": "AWP",
            "joined_at": now_iso(),
            "last_seen": now_iso(),
            "loadout": {
                "username_style": "vip_gold",
                "avatar_frame": "neon_orange",
                "banner_badge": "founder",
                "card_background": "tactical",
                "favorite_weapon": "AWP",
                "background_video": "",
                "personal_quote": "Play to win, not to participate.",
                "profile_theme": "live_identity",
                "profile_song": "dQw4w9WgXcQ"
            },
        }
        await db.users.insert_one(admin)

    # Optional root super-admin seed
    root_password = os.environ.get("ROOT_PASSWORD")
    root_email = os.environ.get("ROOT_EMAIL", "root@indungi.ro").lower()
    if root_password and not await db.users.find_one({"email": root_email}):
        await db.users.insert_one({
            "id": uuid.uuid4().hex,
            "email": root_email,
            "username": "root",
            "username_lower": "root",
            "password_hash": hash_password(root_password),
            "role": "root",
            "is_vip": True,
            "vip_tier": "founder",
            "vip_until": None,
            "points": 99999,
            "message_count": 0,
            "reaction_score": 0,
            "banned": False,
            "avatar_url": "",
            "bio": "Root super-administrator. Highest level access.",
            "location": "Sistem",
            "signature": "// ROOT ACCESS //",
            "favorite_game": "cs2",
            "favorite_weapon": "AWP",
            "joined_at": now_iso(),
            "last_seen": now_iso(),
            "loadout": {
                "username_style": "city_lights",
                "avatar_frame": "rainbow_gif",
                "banner_badge": "founder",
                "card_background": "neon_grid",
                "favorite_weapon": "AWP",
                "background_video": "",
                "personal_quote": "",
                "profile_theme": "arctic_identity",
                "profile_song": ""
            },
        })

    # Optional demo users
    demo_password = os.environ.get("DEMO_USER_PASSWORD")
    seed_demo_users = os.environ.get("SEED_DEMO_USERS", "false").lower() in ("1", "true", "yes")
    demo_users = [
        ("BLINGidg", "bling@topfrag.gg", "AK-47", "vip_gold", "neon_orange", True, "veteran"),
        ("Annoying", "annoying@topfrag.gg", "AWP", "glitch", "neon_purple", True, "veteran"),
        ("Awestruck", "awestruck@topfrag.gg", "Desert Eagle", "city_lights", "neon_cyan", True, "elite"),
        ("Mint", "mint@topfrag.gg", "M4A4", "cool", "neon_cyan", True, "elite"),
        ("zN", "zn@topfrag.gg", "USP-S", "ar", "neon_red", True, "elite"),
        ("JBEX", "jbex@topfrag.gg", "AK-47", "litika", "neon_red", True, "elite"),
        ("Snapplejuice", "snapple@topfrag.gg", "M4A1-S", "vip_gold", "neon_orange", True, "elite"),
        ("NEO_RECRUIT", "neo@topfrag.gg", "Glock-18", "black_mirror", "neon_purple", True, "elite"),
        ("FragMaster", "frag@topfrag.gg", "AK-47", "default", "none", False, None),
        ("HeadshotKing", "hs@topfrag.gg", "AWP", "default", "none", False, None),
        ("RushB", "rushb@topfrag.gg", "AK-47", "default", "none", False, None),
        ("ClutchGod", "clutch@topfrag.gg", "Desert Eagle", "default", "none", False, None),
    ]
    if seed_demo_users and demo_password:
        for username, email, weapon, style, frame, is_vip, tier in demo_users:
            if not await db.users.find_one({"email": email}):
                await db.users.insert_one({
                    "id": uuid.uuid4().hex,
                    "email": email,
                    "username": username,
                    "username_lower": username.lower(),
                    "password_hash": hash_password(demo_password),
                    "role": "user",
                    "is_vip": is_vip,
                    "vip_tier": tier,
                    "vip_until": None,
                    "points": (hash(username) % 1500) + 100,
                    "message_count": (hash(username) % 200) + 10,
                    "reaction_score": (hash(username) % 80),
                    "banned": False,
                    "avatar_url": "",
                    "bio": f"CS player main. {weapon} enthusiast.",
                    "location": ["Romania", "USA", "Germany", "Brazil", "Sweden"][hash(username) % 5],
                    "signature": "",
                    "favorite_game": ["cs16", "cs2", "cs16mix"][hash(username) % 3],
                    "favorite_weapon": weapon,
                    "joined_at": now_iso(),
                    "last_seen": now_iso(),
                    "loadout": {
                        "username_style": style,
                        "avatar_frame": frame,
                        "banner_badge": "veteran" if is_vip else "none",
                        "card_background": "tactical" if is_vip else "default",
                        "favorite_weapon": weapon,
                        "background_video": "",
                        "personal_quote": "",
                        "profile_theme": "default",
                        "profile_song": ""
                    },
                })

    # Boards
    if await db.boards.count_documents({}) == 0:
        boards_data = [
            # CS2
            ("Counter-Strike 2 - General", "Discuss CS2 strategy, updates, and news.", "cs2", "target", 10, None),
            ("CS2 Competitive", "Ranked discussions, MR12 meta, and tournaments.", "cs2", "trophy", 11, None),
            ("CS2 Workshop & Maps", "Custom maps, workshop creations.", "cs2", "map", 12, None),
            # CS 1.6
            ("Counter-Strike 1.6 - General", "Classic CS 1.6 community hub.", "cs16", "crosshair", 20, None),
            ("CS 1.6 Servers", "Find servers, share IPs, recruit admins.", "cs16", "server", 21, None),
            ("CS 1.6 Configs & Cfgs", "Share configs, scripts, rates.", "cs16", "settings", 22, None),
            # CS 1.6 Mix
            ("CS 1.6 Mix - Match Schedule", "Schedule mixes, find players.", "cs16mix", "calendar", 30, None),
            ("CS 1.6 Mix - Demos", "Share & request demos.", "cs16mix", "video", 31, None),
            # General
            ("Announcements", "Forum announcements and news.", "general", "megaphone", 1, None),
            ("Introductions", "Say hi to the community.", "general", "hand", 2, None),
            ("Off-Topic", "Anything not CS related.", "general", "coffee", 3, None),
            ("Tech & Hardware", "Mice, keyboards, monitors, PCs.", "general", "cpu", 4, None),
        ]
        for name, desc, game, icon, order, parent in boards_data:
            await db.boards.insert_one({
                "id": uuid.uuid4().hex,
                "slug": slugify(name),
                "name": name,
                "description": desc,
                "game": game,
                "icon": icon,
                "order": order,
                "parent_id": parent,
                "created_at": now_iso(),
            })

        # Seed sub-forums for "Announcements" and "CS2 Competitive"
        ann = await db.boards.find_one({"slug": slugify("Announcements")}, {"_id": 0})
        comp = await db.boards.find_one({"slug": slugify("CS2 Competitive")}, {"_id": 0})
        sub_data = []
        if ann:
            sub_data += [
                ("News & Updates", "Latest news from the team.", "general", "megaphone", 1, ann["id"]),
                ("Rules", "Forum rules & moderation policy.", "general", "book", 2, ann["id"]),
                ("Changelog", "Site changes and patches.", "general", "list", 3, ann["id"]),
            ]
        if comp:
            sub_data += [
                ("Premier Discussion", "Premier mode discussions.", "cs2", "trophy", 1, comp["id"]),
                ("Tournament Results", "Recent tournament outcomes.", "cs2", "award", 2, comp["id"]),
            ]
        for name, desc, game, icon, order, parent in sub_data:
            await db.boards.insert_one({
                "id": uuid.uuid4().hex,
                "slug": slugify(name),
                "name": name,
                "description": desc,
                "game": game,
                "icon": icon,
                "order": order,
                "parent_id": parent,
                "created_at": now_iso(),
            })

    # Demo topics + posts
    if await db.topics.count_documents({}) == 0:
        boards = await db.boards.find({}, {"_id": 0}).to_list(50)
        users = await db.users.find({"role": "user"}, {"_id": 0, "password_hash": 0}).to_list(50)
        if users and boards:
            sample_topics = [
                ("Welcome to TopFrag - Read this first!", "Welcome to the most competitive CS community forum. Read the rules and introduce yourself in the introductions board.", "general"),
                ("CS2 MR12 - Better or Worse than MR15?", "Honest opinions about the MR12 format change. I personally feel the games are quicker but less strategic.", "cs2"),
                ("Best AWP crosshair in CS2?", "Share your AWP crosshair codes. Looking for something clean.", "cs2"),
                ("CS 1.6 still alive in 2026?", "Anyone still playing CS 1.6 competitively in 2026? Looking for a team.", "cs16"),
                ("Looking for 5v5 mix tonight 21:00 EU", "Need 4 more for a mix tonight. Skill: experienced. Reply or DM.", "cs16mix"),
                ("Show off your gaming setup 2026", "Drop pics of your battle stations.", "general"),
                ("Best AK-47 sprays drill", "Share your favorite training maps for AK spray control.", "cs2"),
                ("Demo: 1v5 clutch on dust2", "Sharing my best clutch from last season.", "cs16mix"),
            ]
            for title, content, game in sample_topics:
                board = next((b for b in boards if b["game"] == game), boards[0])
                author = users[hash(title) % len(users)]
                topic_id = uuid.uuid4().hex
                ts = now_iso()
                await db.topics.insert_one({
                    "id": topic_id,
                    "slug": slugify(title),
                    "title": title,
                    "board_id": board["id"],
                    "board_name": board["name"],
                    "author_id": author["id"],
                    "author_username": author["username"],
                    "pinned": "Welcome" in title,
                    "locked": False,
                    "views": (hash(title) % 500) + 50,
                    "post_count": 1,
                    "created_at": ts,
                    "last_post_at": ts,
                    "last_post_user_id": author["id"],
                    "last_post_username": author["username"],
                })
                await db.posts.insert_one({
                    "id": uuid.uuid4().hex,
                    "topic_id": topic_id,
                    "board_id": board["id"],
                    "author_id": author["id"],
                    "author_username": author["username"],
                    "content": content,
                    "created_at": ts,
                    "reactions": {"like": (hash(title) % 10)},
                    "reaction_users": {},
                })

    # Write non-secret test notes
    creds_path = Path("/app/memory/test_credentials.md")
    creds_path.parent.mkdir(parents=True, exist_ok=True)
    creds_path.write_text(
        "# Test Credentials\n\n"
        "## Admin\n"
        f"- email: {os.environ['ADMIN_EMAIL']}\n"
        "- password: set via ADMIN_PASSWORD environment variable\n"
        "- role: admin\n\n"
        "## Optional Demo Users\n"
        "- enable with SEED_DEMO_USERS=true\n"
        "- password: set via DEMO_USER_PASSWORD environment variable\n\n"
        "## Endpoints\n"
        "- POST /api/auth/register\n"
        "- POST /api/auth/login\n"
        "- GET /api/auth/me (Bearer token)\n"
    )


@app.on_event("startup")
async def on_startup():
    await create_idg_indexes(db)
    await seed()
    await seed_idg(db, now_iso)
    await seed_idg_v2(db, now_iso)
    try:
        init_storage()
    except Exception as e:
        print("Storage init err:", e)
    
    # cron tasks
    asyncio.create_task(monthly_winner_cron(db, now_iso, award_points))
    asyncio.create_task(auto_archive_cron(db, now_iso))
    asyncio.create_task(rss_importer_cron(db, now_iso))
    
    async def steam_news_cron_task():
        while True:
            try:
                await asyncio.sleep(10)
                from idg_news_ext import sync_steam_cs2_news
                await sync_steam_cs2_news(db)
            except Exception as e:
                print("Steam news cron err:", e)
            await asyncio.sleep(1800)
            
    async def real_esports_cron_task():
        while True:
            try:
                await asyncio.sleep(15)
                from idg_tournaments_ext import sync_real_esports_matches
                await sync_real_esports_matches(db)
            except Exception as e:
                print("Real esports sync cron err:", e)
            await asyncio.sleep(900)  # every 15 minutes
            
    asyncio.create_task(steam_news_cron_task())
    asyncio.create_task(real_esports_cron_task())
    print("Startup complete.")

# ----------------- INCLUDE ROUTERS -----------------

# Mount IDG core extensions
idg_core_router = build_idg_core_router(db, get_current_user, require_admin, now_iso)
api.include_router(idg_core_router)
idg_external_router = build_idg_external_router(db)
api.include_router(idg_external_router)

# Mount IDG admin extension (pages, groups, apps, appearance, extended settings, members)
idg_router = build_idg_router(db, get_current_user, require_admin, now_iso, hash_password, clear_groups_cache)
api.include_router(idg_router)

# Mount IDG v2 extension (menus, widgets, blocks, marketplace)
idg_router_v2 = build_idg_router_v2(db, get_current_user, require_admin, now_iso)
api.include_router(idg_router_v2)

# Mount IDG Chat extension
api.include_router(idg_chat_router)
api.include_router(idg_clans_router)
api.include_router(idg_news_router)
api.include_router(idg_tournaments_router)
api.include_router(idg_hltv_router)
api.include_router(idg_downloads_router)
servers_router = build_servers_router()
api.include_router(servers_router)
idg_widgets_router = build_widgets_data_router(get_current_user)
api.include_router(idg_widgets_router)
api.include_router(idg_stats_router)

# Mount MCP Server Extension
api.include_router(build_mcp_router())

@api.get("/widgets/live-metrics")
@api.post("/widgets/live-metrics")
@api.post("/online-stats")
async def get_live_site_metrics():
    online_count = await db.users.count_documents({"last_seen": {"$gte": (datetime.now(timezone.utc) - timedelta(minutes=15)).isoformat()}})
    return {
        "site": max(48, online_count + 12),
        "online_users": max(48, online_count + 12),
        "servers": 74,
        "total_players": 74,
        "server_mods": {
            "PUBLIC": 32,
            "RESPAWN": 22,
            "MIX": 12,
            "FUN": 8
        }
    }

idg_arena_router = build_arena_router(db, get_current_user, get_user_optional)
api.include_router(idg_arena_router)

# Mount IDG Wallet Engine (Coins, Payments, Services, Vouchers, Transactions, Payment Gateways)
from idg_wallet_engine import build_wallet_router
idg_wallet_router = build_wallet_router(db, get_current_user, require_admin, now_iso)
api.include_router(idg_wallet_router)



@app.on_event("shutdown")
async def on_shutdown():
    client.close()


# Mount extension router (uploads, DMs, notifications, search, SSO, board reorder)
ext_router = build_router(db, get_current_user, get_user_optional, require_admin, now_iso, award_points, clear_boards_cache)
api.include_router(ext_router)


@api.post("/idg/import-archive-data")
async def import_archive_data(payload: dict):
    """One-time import of archived forum data from Wayback Machine."""
    # Create root archive board
    archive_root_id = uuid.uuid4().hex
    await db.idg_boards.update_one(
        {"name": "Arhiva 2019"},
        {"$setOnInsert": {
            "id": archive_root_id, "name": "Arhiva 2019",
            "description": "Recuperat din Web Archive - indungi.ro (2019)",
            "game": "general", "icon": "archive", "order": 999,
            "parent_id": None, "min_role": "user", "vip_only": False, "archived": True
        }}, upsert=True
    )
    root_board = await db.idg_boards.find_one({"name": "Arhiva 2019"})
    parent_id = root_board["id"]

    imported = {"forums": 0, "topics": 0, "members": 0}

    for i, forum in enumerate(payload.get("forums", [])):
        name = forum.get("name", "").strip().lstrip("[").strip()
        if not name: continue
        res = await db.idg_boards.update_one(
            {"name": name, "parent_id": parent_id},
            {"$setOnInsert": {
                "id": uuid.uuid4().hex, "name": name,
                "description": f"Arhivat: {forum.get('url', '')}",
                "game": "general", "icon": "folder", "order": i,
                "parent_id": parent_id, "min_role": "user", "vip_only": False, "archived": True
            }}, upsert=True
        )
        if res.upserted_id: imported["forums"] += 1

    for topic in payload.get("topics", []):
        name = topic.get("name", "").strip()
        if not name: continue
        res = await db.idg_topics.update_one(
            {"title": name, "archived": True},
            {"$setOnInsert": {
                "id": uuid.uuid4().hex, "board_id": parent_id, "title": name,
                "author_id": "system", "author_username": "ArchiveBot",
                "content": f"Topic arhivat: {topic.get('url', '')}",
                "created_at": now_iso(), "updated_at": now_iso(),
                "pinned": False, "locked": True, "archived": True,
                "views": 0, "reply_count": 0, "reactions": {}
            }}, upsert=True
        )
        if res.upserted_id: imported["topics"] += 1

    for member in payload.get("members", []):
        username = member.get("name", "").strip()
        if not username: continue
        res = await db.idg_users.update_one(
            {"username": username},
            {"$setOnInsert": {
                "id": uuid.uuid4().hex, "username": username,
                "email": f"{username.lower().replace(' ', '_')}@archive.local",
                "password_hash": "ARCHIVED", "role": "user", "points": 0,
                "created_at": now_iso(), "bio": f"Recuperat din arhiva 2019: {member.get('url', '')}",
                "is_vip": False, "archived": True
            }}, upsert=True
        )
        if res.upserted_id: imported["members"] += 1

    return {"status": "ok", "imported": imported}


@api.get("/dedup")
async def dedup_db():
    print("Deduplicating idg_groups...")
    groups = await db.idg_groups.find().to_list(1000)
    seen_slugs = set()
    deleted_groups = 0
    for g in groups:
        slug = g.get("slug")
        if slug in seen_slugs:
            await db.idg_groups.delete_one({"_id": g["_id"]})
            deleted_groups += 1
        else:
            seen_slugs.add(slug)

    print("Deduplicating idg_apps...")
    apps = await db.idg_apps.find().to_list(1000)
    seen_keys = set()
    deleted_apps = 0
    for a in apps:
        key = a.get("key")
        if key in seen_keys:
            await db.idg_apps.delete_one({"_id": a["_id"]})
            deleted_apps += 1
        else:
            seen_keys.add(key)
            
    return {"deleted_groups": deleted_groups, "deleted_apps": deleted_apps}



class OAuthCallbackIn(BaseModel):
    code: str
    redirect_uri: str

class SteamCallbackIn(BaseModel):
    params: Dict[str, str]

async def make_http_request(url: str, method: str = "GET", data: Optional[dict] = None, headers: Optional[dict] = None) -> Any:
    import urllib.request
    import urllib.parse
    import json
    def _req():
        req_data = None
        req_headers = headers or {}
        if data is not None:
            if req_headers.get("Content-Type") == "application/json":
                req_data = json.dumps(data).encode("utf-8")
            else:
                req_data = urllib.parse.urlencode(data).encode("utf-8")
        
        req = urllib.request.Request(url, data=req_data, headers=req_headers, method=method)
        try:
            with urllib.request.urlopen(req) as response:
                return json.loads(response.read().decode("utf-8"))
        except urllib.error.HTTPError as e:
            err_body = e.read().decode("utf-8")
            raise Exception(f"HTTP {e.code}: {err_body}")
    
    return await asyncio.to_thread(_req)

@api.get("/auth/google/login")
async def get_google_login_url(redirect_uri: str):
    import urllib.parse
    client_id = os.environ.get("GOOGLE_CLIENT_ID", "")
    params = {
        "client_id": client_id,
        "redirect_uri": redirect_uri,
        "response_type": "code",
        "scope": "openid email profile",
        "access_type": "online"
    }
    url = "https://accounts.google.com/o/oauth2/v2/auth?" + urllib.parse.urlencode(params)
    return {"url": url}

@api.get("/auth/discord/login")
async def get_discord_login_url(redirect_uri: str):
    import urllib.parse
    client_id = os.environ.get("DISCORD_CLIENT_ID", "")
    params = {
        "client_id": client_id,
        "redirect_uri": redirect_uri,
        "response_type": "code",
        "scope": "identify email"
    }
    url = "https://discord.com/api/oauth2/authorize?" + urllib.parse.urlencode(params)
    return {"url": url}

@api.get("/auth/steam/login")
async def get_steam_login_url(redirect_uri: str):
    import urllib.parse
    params = {
        "openid.ns": "http://specs.openid.net/auth/2.0",
        "openid.mode": "checkid_setup",
        "openid.return_to": redirect_uri,
        "openid.realm": redirect_uri.split("/auth/callback")[0],
        "openid.identity": "http://specs.openid.net/auth/2.0/identifier_select",
        "openid.claimed_id": "http://specs.openid.net/auth/2.0/identifier_select"
    }
    url = "https://steamcommunity.com/openid/login?" + urllib.parse.urlencode(params)
    return {"url": url}

@api.post("/auth/google/callback")
async def google_callback(body: OAuthCallbackIn):
    google_client_id = os.environ.get("GOOGLE_CLIENT_ID", "")
    google_client_secret = os.environ.get("GOOGLE_CLIENT_SECRET", "")
    
    token_url = "https://oauth2.googleapis.com/token"
    token_data = {
        "code": body.code,
        "client_id": google_client_id,
        "client_secret": google_client_secret,
        "redirect_uri": body.redirect_uri,
        "grant_type": "authorization_code"
    }
    
    try:
        token_res = await make_http_request(token_url, method="POST", data=token_data)
        access_token = token_res.get("access_token")
        if not access_token:
            raise HTTPException(400, "Failed to get access token from Google")
            
        userinfo_url = "https://www.googleapis.com/oauth2/v3/userinfo"
        headers = {"Authorization": f"Bearer {access_token}"}
        userinfo = await make_http_request(userinfo_url, headers=headers)
    except Exception as e:
        raise HTTPException(400, f"Google auth error: {str(e)}")
        
    google_id = userinfo.get("sub")
    email = userinfo.get("email", "").lower()
    name = userinfo.get("name") or userinfo.get("given_name") or "GoogleUser"
    picture = userinfo.get("picture") or ""
    
    if not google_id or not email:
        raise HTTPException(400, "Missing email or Google ID from provider")
        
    user = await db.users.find_one({"google_id": google_id})
    if not user:
        user = await db.users.find_one({"email": email})
        if user:
            await db.users.update_one({"id": user["id"]}, {"$set": {"google_id": google_id, "last_seen": now_iso()}})
        else:
            base_uname = name
            uname = base_uname
            counter = 1
            while await db.users.find_one({"username_lower": uname.lower()}):
                uname = f"{base_uname}_{counter}"
                counter += 1
                
            user = {
                "id": uuid.uuid4().hex,
                "email": email,
                "username": uname,
                "username_lower": uname.lower(),
                "password_hash": bcrypt.hashpw(uuid.uuid4().hex.encode('utf-8'), bcrypt.gensalt()).decode('utf-8'),
                "role": "user",
                "is_vip": False,
                "vip_tier": None,
                "vip_until": None,
                "points": 0,
                "message_count": 0,
                "reaction_score": 0,
                "banned": False,
                "avatar_url": picture,
                "google_id": google_id,
                "joined_at": datetime.now(timezone.utc).isoformat(),
                "last_seen": datetime.now(timezone.utc).isoformat(),
            }
            await db.users.insert_one(user)
    else:
        await db.users.update_one({"id": user["id"]}, {"$set": {"last_seen": now_iso()}})
        
    token = create_token(user["id"])
    return {"token": token, "user": await hydrate_user(clean_user(user))}

@api.post("/auth/discord/callback")
async def discord_callback(body: OAuthCallbackIn):
    discord_client_id = os.environ.get("DISCORD_CLIENT_ID", "")
    discord_client_secret = os.environ.get("DISCORD_CLIENT_SECRET", "")
    
    token_url = "https://discord.com/api/oauth2/token"
    token_data = {
        "client_id": discord_client_id,
        "client_secret": discord_client_secret,
        "grant_type": "authorization_code",
        "code": body.code,
        "redirect_uri": body.redirect_uri
    }
    
    try:
        token_res = await make_http_request(token_url, method="POST", data=token_data)
        access_token = token_res.get("access_token")
        if not access_token:
            raise HTTPException(400, "Failed to get access token from Discord")
            
        userinfo_url = "https://discord.com/api/users/@me"
        headers = {"Authorization": f"Bearer {access_token}"}
        userinfo = await make_http_request(userinfo_url, headers=headers)
    except Exception as e:
        raise HTTPException(400, f"Discord auth error: {str(e)}")
        
    discord_id = userinfo.get("id")
    email = userinfo.get("email", "").lower() if userinfo.get("email") else f"discord_{discord_id}@indungi.pro"
    username = userinfo.get("username", "DiscordUser")
    avatar_hash = userinfo.get("avatar")
    
    avatar = ""
    if discord_id and avatar_hash:
        avatar = f"https://cdn.discordapp.com/avatars/{discord_id}/{avatar_hash}.png"
        
    if not discord_id:
        raise HTTPException(400, "Missing Discord ID from provider")
        
    user = await db.users.find_one({"discord_id": discord_id})
    if not user:
        user = await db.users.find_one({"email": email})
        if user:
            await db.users.update_one({"id": user["id"]}, {"$set": {"discord_id": discord_id, "last_seen": now_iso()}})
        else:
            base_uname = username
            uname = base_uname
            counter = 1
            while await db.users.find_one({"username_lower": uname.lower()}):
                uname = f"{base_uname}_{counter}"
                counter += 1
                
            user = {
                "id": uuid.uuid4().hex,
                "email": email,
                "username": uname,
                "username_lower": uname.lower(),
                "password_hash": bcrypt.hashpw(uuid.uuid4().hex.encode('utf-8'), bcrypt.gensalt()).decode('utf-8'),
                "role": "user",
                "is_vip": False,
                "vip_tier": None,
                "vip_until": None,
                "points": 0,
                "message_count": 0,
                "reaction_score": 0,
                "banned": False,
                "avatar_url": avatar,
                "discord_id": discord_id,
                "joined_at": datetime.now(timezone.utc).isoformat(),
                "last_seen": datetime.now(timezone.utc).isoformat(),
            }
            await db.users.insert_one(user)
    else:
        await db.users.update_one({"id": user["id"]}, {"$set": {"last_seen": now_iso()}})
        
    token = create_token(user["id"])
    return {"token": token, "user": await hydrate_user(clean_user(user))}

@api.post("/auth/steam/callback")
async def steam_callback(body: SteamCallbackIn):
    import urllib.parse
    import urllib.request
    import json
    
    params = {**body.params, "openid.mode": "check_authentication"}
    def _validate():
        req_data = urllib.parse.urlencode(params).encode("utf-8")
        req = urllib.request.Request("https://steamcommunity.com/openid/login", data=req_data, method="POST")
        with urllib.request.urlopen(req) as resp:
            return resp.read().decode("utf-8")
            
    res_text = await asyncio.to_thread(_validate)
    if "is_valid:true" not in res_text.replace(" ", ""):
        raise HTTPException(400, "Steam authentication failed")
        
    claimed_id = body.params.get("openid.claimed_id", "")
    steam_id = claimed_id.split("/id/")[-1].strip("/")
    if not steam_id or not steam_id.isdigit():
        raise HTTPException(400, "Invalid Steam ID")
        
    steam_api_key = os.environ.get("STEAM_API_KEY", "")
    profile = {}
    if steam_api_key:
        try:
            summary_url = f"https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key={steam_api_key}&steamids={steam_id}"
            def _summary():
                with urllib.request.urlopen(summary_url) as r:
                    return json.loads(r.read().decode("utf-8"))
            summary_res = await asyncio.to_thread(_summary)
            players = summary_res.get("response", {}).get("players", [])
            if players:
                profile = players[0]
        except Exception as e:
            print("Steam API summaries err:", e)
            
    username = profile.get("personaname") or f"SteamUser_{steam_id[:6]}"
    avatar = profile.get("avatarfull") or ""
    
    user = await db.users.find_one({"steam_id": steam_id})
    if not user:
        base_uname = username
        uname = base_uname
        counter = 1
        while await db.users.find_one({"username_lower": uname.lower()}):
            uname = f"{base_uname}_{counter}"
            counter += 1
            
        user = {
            "id": uuid.uuid4().hex,
            "email": f"steam_{steam_id}@indungi.pro",
            "username": uname,
            "username_lower": uname.lower(),
            "password_hash": bcrypt.hashpw(uuid.uuid4().hex.encode('utf-8'), bcrypt.gensalt()).decode('utf-8'),
            "role": "user",
            "is_vip": False,
            "vip_tier": None,
            "vip_until": None,
            "points": 0,
            "message_count": 0,
            "reaction_score": 0,
            "banned": False,
            "avatar_url": avatar,
            "steam_id": steam_id,
            "joined_at": datetime.now(timezone.utc).isoformat(),
            "last_seen": datetime.now(timezone.utc).isoformat(),
        }
        await db.users.insert_one(user)
    else:
        await db.users.update_one({"id": user["id"]}, {"$set": {"last_seen": now_iso()}})
        
    token = create_token(user["id"])
    return {"token": token, "user": await hydrate_user(clean_user(user))}

@app.middleware("http")
async def add_no_cache_header(request: Request, call_next):
    response = await call_next(request)
    path = request.url.path
    if "/api" in path and not ("/api/files/" in path or "/files/" in path):
        response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
        response.headers["Pragma"] = "no-cache"
        response.headers["Expires"] = "0"
    return response



@app.get("/testdb")
async def testdb_root():
    return {
        "categories": await db.categories.find({}, {"_id": 0}).to_list(100),
        "groups": await db.groups.find({}, {"_id": 0}).to_list(100),
        "widgets": await db.widgets.find({}, {"_id": 0}).to_list(100)
    }


@api.get("/categories")
async def get_categories():
    cats = await db.categories.find({}, {"_id": 0}).to_list(1000)
    cats.sort(key=lambda x: x.get("order", 0))
    return cats

@api.get("/groups")
async def get_groups():
    grps = await db.groups.find({}, {"_id": 0}).to_list(1000)
    return grps

@api.get("/widgets")
@api.get("/idg/widgets/public")
async def get_widgets():
    w = await db.widgets.find({}, {"_id": 0}).to_list(1000)
    w.sort(key=lambda x: x.get("order", 0))
    return w

from fastapi import Request
@api.api_route("/admin/{collection}/{item_id}", methods=["POST", "PUT", "PATCH", "DELETE"])
@api.api_route("/admin/{collection}", methods=["POST", "PUT", "PATCH"])
async def dynamic_acp_routes(request: Request, collection: str, item_id: str = None, u: dict = Depends(get_current_user)):
    if u.get("role") not in ["root", "admin", "vip"]:
        return JSONResponse(status_code=403, content={"detail": "Forbidden"})
    
    if collection not in ["categories", "groups", "widgets", "settings", "boards", "servers"]:
        raise HTTPException(status_code=404)
        
    method = request.method
    try:
        body = await request.json() if method != "DELETE" else {}
    except:
        body = {}
        
    if method == "DELETE":
        if item_id:
            await db[collection].delete_one({"id": item_id})
        return {"status": "deleted"}
        
    if item_id:
        body["id"] = item_id
        
    if "id" not in body and not item_id:
        if collection == "settings":
            body["id"] = "global"
        else:
            raise HTTPException(status_code=400, detail="Missing ID")
            
    eid = body.get("id")
    existing = await db[collection].find_one({"id": eid}, {"_id": 0})
    
    if existing:
        existing.update(body)
        await db[collection].update_one({"id": eid}, {"$set": existing})
        return existing
    else:
        await db[collection].insert_one(body)
        return body










@api.get("/read-logs")
async def read_logs():
    import os
    if os.path.exists("stderr.log"):
        with open("stderr.log", "r", encoding="utf-8", errors="ignore") as f:
            f.seek(0, 2)
            size = f.tell()
            f.seek(max(0, size - 4000))
            return {"logs": f.read()}
    return {"logs": "not found"}




















@api.get("/wipe-topics")
async def wipe_topics():
    try:
        await db.topics.delete_many({})
        await db.posts.delete_many({})
        
        return {"status": "ok", "message": "Wiped all topics and posts."}
    except Exception as e:
        import traceback
        return {"error": str(e), "trace": traceback.format_exc()}


@api.get("/seed-widgets")
async def seed_widgets():
    try:
        await db.widgets.delete_many({})
        
        widgets = [
            {
                "id": "w_online",
                "type": "online_users",
                "zone": "sidebar",
                "order": 1,
                "title": "Who's Online",
                "config": {"limit": 10},
                "enabled": True
            },
            {
                "id": "w_stats",
                "type": "forum_stats",
                "zone": "sidebar",
                "order": 2,
                "title": "Forum Statistics",
                "config": {},
                "enabled": True
            },
            {
                "id": "w_recent_status",
                "type": "recent_status",
                "zone": "sidebar",
                "order": 3,
                "title": "Recent Status Updates",
                "config": {"limit": 5},
                "enabled": True
            },
            {
                "id": "w_top_contributors",
                "type": "top_contributors",
                "zone": "sidebar",
                "order": 4,
                "title": "Top Contributors",
                "config": {"limit": 5},
                "enabled": True
            },
            {
                "id": "w_recent_topics",
                "type": "recent_topics",
                "zone": "sidebar",
                "order": 5,
                "title": "Recent Topics",
                "config": {"limit": 5},
                "enabled": True
            }
        ]
        
        for w in widgets:
            await db.widgets.insert_one(w)
            
        return {"status": "ok", "message": "IPS-style widgets seeded."}
    except Exception as e:
        import traceback
        return {"error": str(e), "trace": traceback.format_exc()}





@api.get("/patch-js-zones")
async def patch_js_zones():
    try:
        path = "/home/dopebling/public_html/forum/static/js/main.d301fedb.js"
        with open(path, "r", encoding="utf-8") as f:
            js = f.read()
            
        import re
        target = r'\{value:\"sidebar\",label:\"Sidebar\"\}'
        replacement = '{value:"sidebar",label:"Sidebar"},{value:"header",label:"Header"},{value:"footer",label:"Footer"},{value:"content_top",label:"Content Top"},{value:"content_bottom",label:"Content Bottom"}'
        
        new_js = re.sub(target, replacement, js)
        
        if new_js != js:
            with open(path, "w", encoding="utf-8") as f:
                f.write(new_js)
            return {"status": "ok", "message": "Patched JS with new zones!"}
        else:
            return {"status": "error", "message": "Target string not found in JS."}
    except Exception as e:
        import traceback
        return {"error": str(e), "trace": traceback.format_exc()}


from fastapi.responses import HTMLResponse

@api.get("/widget-manager")
async def widget_manager():
    widgets = await db.widgets.find({}, {"_id": 0}).to_list(100)
    
    html = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>IDG Widget Manager Pro</title>
        <style>
            body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #0f131a; color: #fff; padding: 40px; }
            h1 { color: #00eaff; }
            .widget-card { background: #1a202c; padding: 20px; margin-bottom: 15px; border-radius: 8px; border: 1px solid #2d3748; display: flex; justify-content: space-between; align-items: center; }
            .widget-info h3 { margin: 0 0 5px 0; color: #fff; }
            .widget-info p { margin: 0; color: #a0aec0; font-size: 14px; }
            select { padding: 8px; border-radius: 4px; background: #2d3748; color: #fff; border: 1px solid #4a5568; outline: none; }
            button { padding: 8px 16px; background: #00eaff; color: #000; border: none; border-radius: 4px; font-weight: bold; cursor: pointer; }
            button:hover { background: #00c4d6; }
        </style>
    </head>
    <body>
        <h1>⚙️ Advanced Widget Manager (Zones)</h1>
        <p>Interfața grafică principală este limitată la Sidebar. Folosește acest panou avansat pentru a muta instantaneu widget-urile în orice zonă IPS-style (Header, Footer, Content Top etc.). Modificările se aplică instant.</p>
        
        <div id="widgets">
    """
    
    for w in widgets:
        zones = ["sidebar", "header", "footer", "content_top", "content_bottom"]
        options = ""
        for z in zones:
            selected = "selected" if w.get("zone") == z else ""
            options += f'<option value="{z}" {selected}>{z.replace("_", " ").title()}</option>'
            
        html += f"""
        <div class="widget-card">
            <div class="widget-info">
                <h3>{w.get("title", w.get("type"))}</h3>
                <p>ID: {w.get("id")}</p>
            </div>
            <div>
                <select id="select-{w.get("id")}">
                    {options}
                </select>
                <button onclick="saveZone('{w.get("id")}')">Mută</button>
            </div>
        </div>
        """
        
    html += """
        </div>
        <script>
            async function saveZone(id) {
                const zone = document.getElementById('select-' + id).value;
                const btn = document.querySelector(`button[onclick="saveZone('${id}')"]`);
                btn.innerText = 'Salvăm...';
                try {
                    const res = await fetch(`/forum/api/widgets/update-zone?id=${id}&zone=${zone}`);
                    if (res.ok) {
                        btn.innerText = 'Mutat!';
                        setTimeout(() => btn.innerText = 'Mută', 2000);
                    }
                } catch (e) {
                    alert('Eroare la salvare.');
                    btn.innerText = 'Mută';
                }
            }
        </script>
    </body>
    </html>
    """
    return HTMLResponse(content=html)

@api.get("/widgets/update-zone")
async def update_widget_zone(id: str, zone: str):
    await db.widgets.update_one({"id": id}, {"$set": {"zone": zone}})
    return {"status": "ok"}

app.include_router(api)
app.add_middleware(
    CORSMiddleware,
    allow_credentials=True,
    allow_origins=os.environ.get('CORS_ORIGINS', '*').split(','),
    allow_methods=["*"],
    allow_headers=["*"],
)

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')


# reload trigger: 2026-08-16T19:39:50
