from fastapi import APIRouter, Depends, HTTPException, status, Query, Body
from sqlalchemy.orm import Session
from sqlalchemy import func, or_, asc, desc
from typing import List, Optional
from passlib.context import CryptContext
from pydantic import BaseModel, constr, conint
from datetime import datetime

from app.core.database import get_db
from app.models.user import User
from app.schemas.user import UserOut, UserCreate, UserUpdate
from app.core.auth import get_current_user, requires_role_relaxed, requires_role_mgmt

# NEU: Topup-Model importieren, damit Admin-Gutschriften als Topup geloggt werden
from app.models.topup import Topup, TopupStatus

router = APIRouter(prefix="/users", tags=["users"])
pwd = CryptContext(schemes=["bcrypt"], deprecated="auto")

def get_db_sess():
    db = get_db()
    # get_db ist ein generator → FastAPI injiziert automatisch.
    # falls du eine SessionLocal-Funktion hast, nutze die hier analog.
    return db

@router.get("/", response_model=List[UserOut], dependencies=[Depends(requires_role_relaxed("manager","admin"))])
def list_users(
    q: Optional[str] = Query(default=None, description="Suche in Name/E-Mail/Alias"),
    role: Optional[str] = Query(default=None, regex="^(user|manager|admin)$"),
    active: Optional[bool] = Query(default=None),
    balance_lt: Optional[int] = Query(default=None, description="Kontostand < Wert (Cent)"),
    limit: int = Query(default=25, ge=1, le=200),
    offset: int = Query(default=0, ge=0),
    sort: str = Query(default="name", description="name|email|role|balance_cents|is_active"),
    order: str = Query(default="asc", regex="^(asc|desc)$"),
    db: Session = Depends(get_db),
    _: User = Depends(get_current_user),
):
    qy = db.query(User)
    if q:
        like = f"%{q.lower()}%"
        qy = qy.filter(or_(
            func.lower(User.name).like(like),
            func.lower(User.email).like(like),
            func.lower(func.coalesce(User.alias, "")).like(like),
        ))
    if role:
        qy = qy.filter(User.role == role)
    if active is not None:
        qy = qy.filter(User.is_active == active)
    if balance_lt is not None:
        qy = qy.filter((User.balance_cents != None) & (User.balance_cents < balance_lt))

    sort_map = {
        "name": User.name,
        "email": User.email,
        "role": User.role,
        "balance_cents": User.balance_cents,
        "is_active": User.is_active,
    }
    col = sort_map.get(sort, User.name)
    qy = qy.order_by(asc(col) if order == "asc" else desc(col))

    return qy.limit(limit).offset(offset).all()

@router.get("/{user_id}", response_model=UserOut, dependencies=[Depends(requires_role_relaxed("manager","admin"))])
def get_user(user_id: int, db: Session = Depends(get_db), _: User = Depends(get_current_user)):
    u = db.query(User).filter(User.id == user_id).first()
    if not u:
        raise HTTPException(status_code=404, detail="User not found")
    return u

@router.post("/", response_model=UserOut, status_code=status.HTTP_201_CREATED, dependencies=[Depends(requires_role_mgmt("manager","admin"))])
def create_user(payload: UserCreate, db: Session = Depends(get_db), admin: User = Depends(get_current_user)):
    if db.query(User).filter(User.email == payload.email).first():
        raise HTTPException(status_code=409, detail="E-Mail already exists")
    if payload.alias and db.query(User).filter(User.alias == payload.alias).first():
        raise HTTPException(status_code=409, detail="Alias already exists")
    u = User(
        name=payload.name,
        email=payload.email,
        hashed_password=pwd.hash(payload.password),
        hashed_pin=pwd.hash(payload.pin),
        alias=payload.alias,
        paypal_email=payload.paypal_email,
        role=payload.role,
        is_active=True,
    )
    db.add(u); db.commit(); db.refresh(u)
    return u

@router.patch("/{user_id}", response_model=UserOut, dependencies=[Depends(requires_role_mgmt("manager","admin"))])
def update_user(user_id: int, payload: UserUpdate, db: Session = Depends(get_db), admin: User = Depends(get_current_user)):
    u = db.query(User).filter(User.id == user_id).first()
    if not u:
        raise HTTPException(status_code=404, detail="User not found")

    if payload.email is not None:
        if db.query(User).filter(User.email == payload.email, User.id != user_id).first():
            raise HTTPException(status_code=409, detail="E-Mail already exists")
        u.email = payload.email
    if payload.alias is not None:
        if db.query(User).filter(User.alias == payload.alias, User.id != user_id).first():
            raise HTTPException(status_code=409, detail="Alias already exists")
        u.alias = payload.alias
    if payload.name is not None:
        u.name = payload.name
    if payload.password is not None:
        u.hashed_password = pwd.hash(payload.password)
    if payload.pin is not None:
        u.hashed_pin = pwd.hash(payload.pin)
    if payload.paypal_email is not None:
        u.paypal_email = payload.paypal_email
    if payload.role is not None:
        u.role = payload.role
    if payload.is_active is not None:
        u.is_active = payload.is_active

    db.commit(); db.refresh(u)
    return u

@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(requires_role_mgmt("manager","admin"))])
def delete_user(user_id: int, db: Session = Depends(get_db), admin: User = Depends(get_current_user)):
    u = db.query(User).filter(User.id == user_id).first()
    if not u:
        raise HTTPException(status_code=404, detail="User not found")
    db.delete(u); db.commit()
    return None

# -----------------------------
# Sicherheitsaktionen
# -----------------------------

class SetPinIn(BaseModel):
    pin: constr(min_length=6, max_length=6)

class SetPasswordIn(BaseModel):
    password: constr(min_length=8)

@router.post("/{user_id}/set-pin", dependencies=[Depends(requires_role_mgmt("manager","admin"))])
def set_pin(user_id: int, payload: SetPinIn, db: Session = Depends(get_db), admin: User = Depends(get_current_user)):
    u = db.query(User).filter(User.id == user_id).first()
    if not u: raise HTTPException(status_code=404, detail="User not found")
    if admin.role == "manager" and u.role == "admin":
        raise HTTPException(status_code=403, detail="Managers cannot modify admin credentials")
    u.hashed_pin = pwd.hash(payload.pin); db.commit()
    return {"status": "ok"}

@router.post("/{user_id}/set-password", dependencies=[Depends(requires_role_mgmt("manager","admin"))])
def set_password(user_id: int, payload: SetPasswordIn, db: Session = Depends(get_db), admin: User = Depends(get_current_user)):
    u = db.query(User).filter(User.id == user_id).first()
    if not u: raise HTTPException(status_code=404, detail="User not found")
    if admin.role == "manager" and u.role == "admin":
        raise HTTPException(status_code=403, detail="Managers cannot modify admin credentials")
    u.hashed_password = pwd.hash(payload.password); db.commit()
    return {"status": "ok"}

class BalanceAdjustIn(BaseModel):
    amount_cents: conint(strict=True)
    reason: constr(min_length=1, max_length=500)

@router.post("/{user_id}/adjust-balance", dependencies=[Depends(requires_role_mgmt("manager","admin"))])
def adjust_balance(user_id: int, payload: BalanceAdjustIn, db: Session = Depends(get_db), admin: User = Depends(get_current_user)):
    """
    Admin/Manager passt das Guthaben an.
    - Betrag != 0 erforderlich.
    - Bei positiver Anpassung wird zusätzlich ein Topup mit status=confirmed und note=reason erstellt,
      damit es in /topups/me (und der TransactionPage) sichtbar ist.
    """
    amount = int(payload.amount_cents or 0)
    if amount == 0:
        raise HTTPException(status_code=400, detail="amount_cents must be non-zero")

    u = db.query(User).filter(User.id == user_id).first()
    if not u:
        raise HTTPException(status_code=404, detail="User not found")
    if admin.role == "manager" and u.role == "admin":
        raise HTTPException(status_code=403, detail="Managers cannot adjust admin accounts")

    before = int(u.balance_cents or 0)
    u.balance_cents = before + amount

    created_topup_id: Optional[int] = None

    # Nur bei Gutschrift (+) zusätzlich Topup schreiben (mit Notiz = reason).
    if amount > 0:
        t = Topup(
            user_id=u.id,
            amount_cents=amount,
            status=TopupStatus.confirmed,     # direkt bestätigt
            created_at=datetime.utcnow(),
            confirmed_at=datetime.utcnow(),
            note=payload.reason.strip() if payload.reason else None,
        )
        db.add(t)
        db.flush()  # ID holen, ohne Zwischen-Commit
        created_topup_id = t.id

    db.commit()
    db.refresh(u)

    return {
        "status": "ok",
        "user_id": u.id,
        "old_balance_cents": before,
        "new_balance_cents": u.balance_cents,
        "created_topup_id": created_topup_id,  # nur bei positiver Anpassung gesetzt
    }

# -----------------------------
# FAVORITES: GET / PUT / PATCH
# -----------------------------

def _ensure_can_edit(target_user_id: int, actor: User):
    """Eigenen Datensatz immer; sonst nur manager/admin."""
    if actor.id != target_user_id and str(actor.role) not in {"manager", "admin"}:
        raise HTTPException(status_code=403, detail="Not allowed")

@router.get("/{user_id}/favorites", response_model=List[int])
def get_user_favorites(
    user_id: int,
    db: Session = Depends(get_db),
    current: User = Depends(get_current_user),
):
    _ensure_can_edit(user_id, current)
    u = db.query(User).filter(User.id == user_id).first()
    if not u:
        raise HTTPException(status_code=404, detail="User not found")
    return list(u.favorites or [])

@router.put("/{user_id}/favorites", response_model=List[int])
def replace_user_favorites(
    user_id: int,
    favorites: List[int] = Body(..., embed=False, description="Array von Produkt-IDs"),
    db: Session = Depends(get_db),
    current: User = Depends(get_current_user),
):
    _ensure_can_edit(user_id, current)
    u = db.query(User).filter(User.id == user_id).first()
    if not u:
        raise HTTPException(status_code=404, detail="User not found")

    # Sanitizing: nur eindeutige, positive ints
    clean = sorted({int(x) for x in favorites if isinstance(x, int) and x > 0})
    u.favorites = clean
    db.add(u)
    db.commit()
    return clean

@router.patch("/{user_id}/favorites", response_model=List[int])
def patch_user_favorites(
    user_id: int,
    favorites: List[int] = Body(..., embed=False, description="Array von Produkt-IDs"),
    db: Session = Depends(get_db),
    current: User = Depends(get_current_user),
):
    # Dein Frontend sendet bei PATCH das komplette Array → identisch zu PUT behandeln.
    return replace_user_favorites(user_id, favorites, db, current)
