from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func

from app.database import get_db
from app.models.category import Category
from app.models.user import User
from app.schemas.category import CategoryCreate, CategoryUpdate, CategoryOut
from app.deps import get_current_user, require_role
from app.utils import auto_code

router = APIRouter(prefix="/categories", tags=["categories"])


@router.get("", response_model=list[CategoryOut])
async def list_categories(
    search: Optional[str] = Query(None),
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    q = select(Category).order_by(Category.name)
    if search:
        q = q.where(Category.name.ilike(f"%{search}%"))
    result = await db.execute(q)
    return result.scalars().all()


@router.post("", response_model=CategoryOut, status_code=201)
async def create_category(
    body: CategoryCreate,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(get_current_user),
):
    code = (body.code or auto_code(body.name)).upper()

    existing = await db.execute(select(Category).where(Category.code == code))
    if existing.scalar_one_or_none():
        cnt = await db.scalar(select(func.count()).select_from(Category))
        code = code + str((cnt or 0) + 1)

    cat = Category(name=body.name, code=code)
    db.add(cat)
    try:
        await db.commit()
        await db.refresh(cat)
    except Exception:
        await db.rollback()
        raise
    return cat


@router.put("/{category_id}", response_model=CategoryOut)
async def update_category(
    category_id: int,
    body: CategoryUpdate,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(require_role("admin")),
):
    result = await db.execute(select(Category).where(Category.id == category_id))
    cat = result.scalar_one_or_none()
    if not cat:
        raise HTTPException(status_code=404, detail="Category not found")

    if body.name is not None:
        cat.name = body.name
    if body.code is not None:
        cat.code = body.code.upper()

    try:
        await db.commit()
        await db.refresh(cat)
    except Exception:
        await db.rollback()
        raise
    return cat


@router.delete("/{category_id}", status_code=204)
async def delete_category(
    category_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(require_role("admin")),
):
    result = await db.execute(select(Category).where(Category.id == category_id))
    cat = result.scalar_one_or_none()
    if not cat:
        raise HTTPException(status_code=404, detail="Category not found")

    try:
        await db.delete(cat)
        await db.commit()
    except Exception:
        await db.rollback()
        raise
