"""API administrativa (usada pelo painel web independente e pelo painel mobile legado)."""
import uuid
from datetime import datetime, timedelta, timezone

from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile
from fastapi.responses import PlainTextResponse
from pydantic import ValidationError

from core import (MAX_UPLOAD_MB, NO_ID, UPLOAD_DIR, LoginIn, audit, authenticate, create_session, db, decrypt,
                  encrypt, enforce_password, get_admin_any, hash_password, now, now_iso, public_user, require_admin,
                  revoke_current, revoke_sessions, verify_password)
from models import COLLECTION_MODELS, ChangePasswordIn, ConfigIn, RenewIn, UserCreate, UserUpdate

router = APIRouter(prefix="/api/admin")
META = ("_id", "id", "created_at", "updated_at", "key", "has_management_secret")
VPN_HIDDEN = {"_id": 0, "management_secret_enc": 0}


def validate(model, data: dict) -> dict:
    try:
        return model.model_validate(data).model_dump()
    except ValidationError as e:
        msgs = []
        for err in e.errors()[:5]:
            loc = ".".join(str(x) for x in err["loc"])
            msgs.append(f"{loc}: {err['msg'].replace('Value error, ', '')}")
        raise HTTPException(422, "Dados inválidos — " + "; ".join(msgs))


def clean(doc: dict) -> dict:
    return {k: v for k, v in doc.items() if k not in META}


# ---------------- Auth ----------------
@router.post("/login")
async def admin_login(body: LoginIn, request: Request):
    user = await authenticate(body, "admin", request)
    token, exp = await create_session(user, "admin", request)
    return {"token": token, "user": public_user(user), "expires_at": exp,
            "must_change_password": bool(user.get("must_change_password"))}


@router.post("/logout")
async def admin_logout(admin: dict = Depends(get_admin_any)):
    await revoke_current(admin)
    return {"ok": True}


@router.get("/me")
async def admin_me(admin: dict = Depends(get_admin_any)):
    return public_user(admin)


@router.post("/change-password")
async def change_password(body: ChangePasswordIn, admin: dict = Depends(get_admin_any)):
    full = await db.users.find_one({"id": admin["id"]}, NO_ID)
    if not verify_password(body.current_password, full["password_hash"]):
        raise HTTPException(400, "Senha atual incorreta")
    if body.new_password == body.current_password:
        raise HTTPException(422, "A nova senha deve ser diferente da atual")
    enforce_password(body.new_password, admin=True)
    await db.users.update_one({"id": admin["id"]}, {"$set": {
        "password_hash": hash_password(body.new_password), "must_change_password": False, "password_changed_at": now_iso()}})
    revoked = await revoke_sessions(admin["id"], except_jti=admin["_session"])
    await audit(admin, "change_password", "users", admin["id"])
    return {"ok": True, "revoked_sessions": revoked}


# ---------------- Dashboard / logs ----------------
@router.get("/stats")
async def stats(admin: dict = Depends(require_admin)):
    out = {c: await db[c].count_documents({}) for c in [*COLLECTION_MODELS, "users", "vpn_devices"]}
    out["users_active"] = await db.users.count_documents({"active": True, "role": "user"})
    out["users_expired"] = await db.users.count_documents({"role": "user", "expires_at": {"$ne": None, "$lt": now_iso()}})
    out["sessions_active"] = await db.sessions.count_documents({"revoked_at": None, "expires_at": {"$gt": now()}})
    out["failed_logins_24h"] = await db.security_log.count_documents({"ok": False, "at": {"$gt": now() - timedelta(days=1)}})
    return out


def _iso(docs):
    for d in docs:
        if isinstance(d.get("at"), datetime):
            d["at"] = d["at"].replace(tzinfo=timezone.utc).isoformat()
    return docs


@router.get("/audit")
async def audit_log(admin: dict = Depends(require_admin)):
    return _iso(await db.audit_log.find({}, NO_ID).sort("at", -1).to_list(200))


@router.get("/security-log")
async def security(admin: dict = Depends(require_admin)):
    return _iso(await db.security_log.find({}, NO_ID).sort("at", -1).to_list(200))


# ---------------- Config ----------------
@router.get("/config")
async def get_config(admin: dict = Depends(require_admin)):
    cfg = await db.config.find_one({"key": "app"}, NO_ID) or {}
    return validate(ConfigIn, {**ConfigIn().model_dump(), **clean(cfg)})


@router.put("/config")
async def put_config(body: dict, admin: dict = Depends(require_admin)):
    current = clean(await db.config.find_one({"key": "app"}, NO_ID) or {})
    data = validate(ConfigIn, {**ConfigIn().model_dump(), **current, **clean(body)})
    await db.config.update_one({"key": "app"}, {"$set": {**data, "updated_at": now_iso()}}, upsert=True)
    await audit(admin, "update", "config")
    return data


# ---------------- Upload (armazenamento local no servidor) ----------------
SIGNATURES = {b"\xff\xd8\xff": ".jpg", b"\x89PNG\r\n\x1a\n": ".png", b"GIF8": ".gif"}


@router.post("/upload")
async def upload(file: UploadFile = File(...), admin: dict = Depends(require_admin)):
    data = await file.read(MAX_UPLOAD_MB * 1024 * 1024 + 1)
    if len(data) > MAX_UPLOAD_MB * 1024 * 1024:
        raise HTTPException(413, f"Arquivo maior que {MAX_UPLOAD_MB} MB")
    ext = next((e for sig, e in SIGNATURES.items() if data.startswith(sig)), None)
    if not ext and data[:4] == b"RIFF" and data[8:12] == b"WEBP":
        ext = ".webp"
    if not ext:
        raise HTTPException(415, "Formato não suportado. Envie JPG, PNG, WEBP ou GIF.")
    name = f"{uuid.uuid4().hex}{ext}"
    UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
    (UPLOAD_DIR / name).write_bytes(data)
    await audit(admin, "upload", "media", name)
    return {"url": f"/api/media/{name}", "size": len(data)}


# ---------------- Users ----------------
@router.get("/users")
async def list_users(admin: dict = Depends(require_admin)):
    users = await db.users.find({}, NO_ID).sort("created_at", -1).to_list(5000)
    counts = {d["_id"]: d["n"] for d in await db.sessions.aggregate([
        {"$match": {"revoked_at": None, "expires_at": {"$gt": now()}}}, {"$group": {"_id": "$user_id", "n": {"$sum": 1}}}]).to_list(5000)}
    return [{**public_user(u), "active_sessions": counts.get(u["id"], 0)} for u in users]


@router.post("/users")
async def create_user(body: dict, admin: dict = Depends(require_admin)):
    data = validate(UserCreate, body)
    enforce_password(data["password"], admin=data["role"] == "admin")
    if await db.users.find_one({"username": data["username"]}):
        raise HTTPException(409, "Usuário já existe")
    doc = {**data, "id": str(uuid.uuid4()), "name": data["name"] or data["username"], "created_at": now_iso(),
           "password_hash": hash_password(data.pop("password")), "must_change_password": data["role"] == "admin"}
    doc.pop("password", None)
    await db.users.insert_one(dict(doc))
    await audit(admin, "create", "users", doc["id"], doc["username"])
    return public_user(doc)


@router.put("/users/{uid}")
async def update_user(uid: str, body: dict, admin: dict = Depends(require_admin)):
    data = validate(UserUpdate, body)
    target = await db.users.find_one({"id": uid}, NO_ID)
    if not target:
        raise HTTPException(404, "Usuário não encontrado")
    if uid == admin["id"] and (data.get("active") is False or data.get("role") == "user"):
        raise HTTPException(400, "Você não pode bloquear ou rebaixar a si mesmo")
    upd = {k: v for k, v in data.items() if v is not None and k != "password"}
    if "expires_at" in body and not body.get("expires_at"):
        upd["expires_at"] = None
    revoke = False
    if data.get("password"):
        role = data.get("role") or target["role"]
        enforce_password(data["password"], admin=role == "admin")
        upd["password_hash"] = hash_password(data["password"])
        upd["must_change_password"] = role == "admin" and uid != admin["id"]
        revoke = True
    if data.get("active") is False or (data.get("role") == "user" and target["role"] == "admin"):
        revoke = True
    await db.users.update_one({"id": uid}, {"$set": upd})
    if revoke:
        await revoke_sessions(uid, except_jti=admin["_session"] if uid == admin["id"] else None)
    await audit(admin, "update", "users", uid, ",".join(sorted(upd.keys() - {"password_hash"})) + (",password" if data.get("password") else ""))
    return public_user(await db.users.find_one({"id": uid}, NO_ID))


@router.post("/users/{uid}/renew")
async def renew_user(uid: str, body: RenewIn, admin: dict = Depends(require_admin)):
    u = await db.users.find_one({"id": uid}, NO_ID)
    if not u:
        raise HTTPException(404, "Usuário não encontrado")
    base = now()
    if u.get("expires_at"):
        cur = datetime.fromisoformat(u["expires_at"].replace("Z", "+00:00"))
        base = max(base, cur if cur.tzinfo else cur.replace(tzinfo=timezone.utc))
    new_exp = (base + timedelta(days=body.days)).isoformat()
    await db.users.update_one({"id": uid}, {"$set": {"expires_at": new_exp, "active": True}})
    await audit(admin, "renew", "users", uid, f"+{body.days} dias")
    return public_user(await db.users.find_one({"id": uid}, NO_ID))


@router.get("/users/{uid}/sessions")
async def user_sessions(uid: str, admin: dict = Depends(require_admin)):
    docs = await db.sessions.find({"user_id": uid, "revoked_at": None, "expires_at": {"$gt": now()}}, NO_ID).sort("created_at", -1).to_list(100)
    return [{"id": d["id"], "kind": d["kind"], "ip": d["ip"], "user_agent": d["user_agent"],
             "created_at": d["created_at"].replace(tzinfo=timezone.utc).isoformat(),
             "expires_at": d["expires_at"].replace(tzinfo=timezone.utc).isoformat()} for d in docs]


@router.post("/users/{uid}/revoke-sessions")
async def revoke_user_sessions(uid: str, admin: dict = Depends(require_admin)):
    n = await revoke_sessions(uid, except_jti=admin["_session"] if uid == admin["id"] else None)
    await audit(admin, "revoke_sessions", "users", uid, str(n))
    return {"revoked": n}


@router.delete("/users/{uid}")
async def delete_user(uid: str, admin: dict = Depends(require_admin)):
    if uid == admin["id"]:
        raise HTTPException(400, "Você não pode excluir a si mesmo")
    res = await db.users.delete_one({"id": uid})
    if not res.deleted_count:
        raise HTTPException(404, "Usuário não encontrado")
    for col in ("favorites", "history", "vpn_devices"):
        await db[col].delete_many({"user_id": uid})
    await revoke_sessions(uid)
    await audit(admin, "delete", "users", uid)
    return {"ok": True}


# ---------------- VPN peers (para configurar o servidor WireGuard) ----------------
@router.get("/vpn_servers/{sid}/peers")
async def vpn_peers(sid: str, admin: dict = Depends(require_admin)):
    return await db.vpn_devices.find({"server_id": sid}, {"_id": 0, "psk_enc": 0}).sort("created_at", -1).to_list(5000)


@router.get("/vpn_servers/{sid}/peers/export", response_class=PlainTextResponse)
async def vpn_peers_export(sid: str, admin: dict = Depends(require_admin)):
    lines = [f"# PLAY CHANNELS — peers do servidor {sid} — gerado em {now_iso()}"]
    for d in await db.vpn_devices.find({"server_id": sid}, NO_ID).to_list(5000):
        lines += ["", f"# {d['username']} / {d.get('device_name') or d['device_id']}", "[Peer]",
                  f"PublicKey = {d['public_key']}", f"PresharedKey = {decrypt(d['psk_enc'])}", f"AllowedIPs = {d['address']}/32"]
    await audit(admin, "export_peers", "vpn_servers", sid)
    return "\n".join(lines) + "\n"


@router.delete("/vpn_devices/{did}")
async def vpn_device_delete(did: str, admin: dict = Depends(require_admin)):
    res = await db.vpn_devices.delete_one({"id": did})
    if not res.deleted_count:
        raise HTTPException(404, "Dispositivo não encontrado")
    await audit(admin, "delete", "vpn_devices", did)
    return {"ok": True}


# ---------------- Generic content CRUD (validated) ----------------
def check_col(col: str):
    if col not in COLLECTION_MODELS:
        raise HTTPException(404, "Coleção inválida")


def prepare(col: str, data: dict, existing: dict | None) -> dict:
    if col == "content":
        for s in data.get("seasons", []):
            for ep in s["episodes"]:
                ep["id"] = ep.get("id") or str(uuid.uuid4())
    if col == "vpn_servers":
        secret = data.pop("management_secret", None)
        if secret:
            data["management_secret_enc"] = encrypt(secret)
    return data


def present(col: str, doc: dict) -> dict:
    if col == "vpn_servers":
        doc["has_management_secret"] = bool(doc.pop("management_secret_enc", None))
    return doc


@router.get("/{col}")
async def admin_list(col: str, admin: dict = Depends(require_admin)):
    check_col(col)
    return [present(col, d) for d in await db[col].find({}, NO_ID).to_list(5000)]


@router.post("/{col}")
async def admin_create(col: str, body: dict, admin: dict = Depends(require_admin)):
    check_col(col)
    data = prepare(col, validate(COLLECTION_MODELS[col], clean(body)), None)
    doc = {**data, "id": str(uuid.uuid4()), "created_at": now_iso()}
    await db[col].insert_one(dict(doc))
    await audit(admin, "create", col, doc["id"], doc.get("title") or doc.get("name"))
    return present(col, doc)


@router.put("/{col}/{iid}")
async def admin_update(col: str, iid: str, body: dict, admin: dict = Depends(require_admin)):
    check_col(col)
    existing = await db[col].find_one({"id": iid}, NO_ID)
    if not existing:
        raise HTTPException(404, "Item não encontrado")
    base = {k: v for k, v in clean(existing).items() if k in COLLECTION_MODELS[col].model_fields}
    data = prepare(col, validate(COLLECTION_MODELS[col], {**base, **clean(body)}), existing)
    await db[col].update_one({"id": iid}, {"$set": {**data, "updated_at": now_iso()}})
    await audit(admin, "update", col, iid, data.get("title") or data.get("name"))
    return present(col, await db[col].find_one({"id": iid}, NO_ID))


@router.delete("/{col}/{iid}")
async def admin_delete(col: str, iid: str, admin: dict = Depends(require_admin)):
    check_col(col)
    res = await db[col].delete_one({"id": iid})
    if not res.deleted_count:
        raise HTTPException(404, "Item não encontrado")
    if col == "categories":
        await db.content.update_many({}, {"$pull": {"category_ids": iid}})
    if col == "content":
        await db.favorites.delete_many({"content_id": iid})
        await db.banners.update_many({"content_id": iid}, {"$set": {"content_id": None}})
    if col == "vpn_servers":
        await db.vpn_devices.delete_many({"server_id": iid})
    await audit(admin, "delete", col, iid)
    return {"ok": True}
