"""/api/admin/* — user management (admin role only)."""
from __future__ import annotations

import json as _json
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy import text
from sqlalchemy.orm import Session

from backend.api.middleware.auth import require_role
from backend.models.database import get_db
from backend.services import auth_service

router = APIRouter(
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(require_role("admin"))],
)


# ── schemas ──────────────────────────────────────────────────────────────────

class UserRow(BaseModel):
    id: int
    username: str
    display_name: Optional[str] = None
    role: str
    type: Optional[str] = None
    channel: Optional[str] = None
    categories: Optional[list[str] | str] = None
    buyers: Optional[list[str]] = None
    active: bool = True
    has_password: bool = False


class UserCreate(BaseModel):
    username: str = Field(..., min_length=1, max_length=64)
    display_name: Optional[str] = None
    role: str
    type: Optional[str] = None
    channel: Optional[str] = None
    categories: Optional[list[str] | str] = None
    buyers: Optional[list[str]] = None
    active: bool = True
    password: Optional[str] = None  # if None, user enters dev-mode (any pw works)


class UserUpdate(BaseModel):
    display_name: Optional[str] = None
    role: Optional[str] = None
    type: Optional[str] = None
    channel: Optional[str] = None
    categories: Optional[list[str] | str] = None
    buyers: Optional[list[str]] = None
    active: Optional[bool] = None


class ResetPasswordRequest(BaseModel):
    new_password: str = Field(..., min_length=6)


# ── helpers ──────────────────────────────────────────────────────────────────

def _row_to_user(r) -> UserRow:
    cats_raw = r[6]
    buys_raw = r[7]
    cats: list[str] | str | None = None
    if cats_raw is not None and cats_raw != "null":
        try:
            cats = _json.loads(cats_raw) if isinstance(cats_raw, str) else cats_raw
        except Exception:
            cats = None
    buys: list[str] | None = None
    if buys_raw is not None and buys_raw != "null":
        try:
            buys = _json.loads(buys_raw) if isinstance(buys_raw, str) else buys_raw
        except Exception:
            buys = None
    return UserRow(
        id=r[0], username=r[1], display_name=r[2], role=r[3] or "",
        type=r[4], channel=r[5], categories=cats, buyers=buys,
        active=bool(r[8]) if r[8] is not None else True,
        has_password=bool(r[9]),
    )


_SELECT_USER = """
    SELECT id, username, display_name, role, type, channel,
           categories::text, buyers::text,
           COALESCE(active, true) AS active,
           (password_hash IS NOT NULL AND password_hash <> '') AS has_password
    FROM users
"""


# ── endpoints ────────────────────────────────────────────────────────────────

@router.get("/users", response_model=list[UserRow])
def list_users(db: Session = Depends(get_db)) -> list[UserRow]:
    rows = db.execute(text(_SELECT_USER + " ORDER BY role, username")).fetchall()
    return [_row_to_user(r) for r in rows]


@router.post("/users", response_model=UserRow, status_code=201)
def create_user(body: UserCreate, db: Session = Depends(get_db)) -> UserRow:
    existing = db.execute(
        text("SELECT 1 FROM users WHERE LOWER(username)=LOWER(:u)"),
        {"u": body.username},
    ).fetchone()
    if existing:
        raise HTTPException(status_code=409, detail="Username already exists")

    pwd_hash = auth_service.hash_password(body.password) if body.password else None
    cats_json = _json.dumps(body.categories) if body.categories is not None else None
    buys_json = _json.dumps(body.buyers) if body.buyers is not None else None

    new_id = db.execute(
        text("""
            INSERT INTO users
                (username, display_name, password_hash, role, type, channel,
                 categories, buyers, active)
            VALUES (:u, :d, :h, :r, :t, :c,
                    CAST(:cats AS jsonb), CAST(:buys AS jsonb), :a)
            RETURNING id
        """),
        {
            "u": body.username, "d": body.display_name, "h": pwd_hash,
            "r": body.role, "t": body.type, "c": body.channel,
            "cats": cats_json, "buys": buys_json, "a": body.active,
        },
    ).scalar()
    db.commit()
    row = db.execute(text(_SELECT_USER + " WHERE id = :uid"),
                     {"uid": new_id}).fetchone()
    return _row_to_user(row)


@router.put("/users/{user_id}", response_model=UserRow)
def update_user(user_id: int, body: UserUpdate,
                db: Session = Depends(get_db)) -> UserRow:
    fields: list[str] = []
    params: dict = {"uid": user_id}
    if body.display_name is not None:
        fields.append("display_name = :d"); params["d"] = body.display_name
    if body.role is not None:
        fields.append("role = :r"); params["r"] = body.role
    if body.type is not None:
        fields.append("type = :t"); params["t"] = body.type
    if body.channel is not None:
        fields.append("channel = :c"); params["c"] = body.channel
    if body.categories is not None:
        fields.append("categories = CAST(:cats AS jsonb)")
        params["cats"] = _json.dumps(body.categories)
    if body.buyers is not None:
        fields.append("buyers = CAST(:buys AS jsonb)")
        params["buys"] = _json.dumps(body.buyers)
    if body.active is not None:
        fields.append("active = :a"); params["a"] = body.active

    if not fields:
        raise HTTPException(status_code=400, detail="No fields to update")

    res = db.execute(
        text(f"UPDATE users SET {', '.join(fields)} WHERE id = :uid"),
        params,
    )
    if res.rowcount == 0:
        raise HTTPException(status_code=404, detail="User not found")
    db.commit()
    row = db.execute(text(_SELECT_USER + " WHERE id = :uid"),
                     {"uid": user_id}).fetchone()
    return _row_to_user(row)


@router.put("/users/{user_id}/reset-password")
def reset_password(user_id: int, body: ResetPasswordRequest,
                   db: Session = Depends(get_db)) -> dict:
    ok = auth_service.set_password(db, user_id, body.new_password)
    if not ok:
        raise HTTPException(status_code=404, detail="User not found")
    return {"ok": True}
