"""Requisitions — purchase requests raised by staff/manager, decided by the admin."""
from datetime import date

from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session, selectinload

from app.api.deps import client_ip, get_current_user, require_admin, require_write
from app.core.time import range_bounds_utc, utcnow
from app.db.session import get_db
from app.models.enums import RequisitionStatus
from app.models.requisition import Requisition, RequisitionItem, RequisitionStatusHistory
from app.models.user import User
from app.schemas.common import Message, Page
from app.schemas.requisition import (
    RequisitionCreate,
    RequisitionDetail,
    RequisitionListItem,
    RequisitionStatusChange,
    RequisitionStatusCount,
    RequisitionSummary,
    RequisitionUpdate,
)
from app.services import audit
from app.services import requisitions as workflow
from app.services.numbering import next_number

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

_DETAIL_LOADS = (
    selectinload(Requisition.items),
    selectinload(Requisition.status_history),
)


def _get_or_404(db: Session, requisition_id: int, *, detail: bool = False) -> Requisition:
    stmt = select(Requisition).where(
        Requisition.id == requisition_id,
        Requisition.is_deleted.is_(False),
    )
    if detail:
        stmt = stmt.options(*_DETAIL_LOADS)
    requisition = db.scalar(stmt)
    if requisition is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Requisition not found")
    return requisition


def _as_detail(requisition: Requisition) -> RequisitionDetail:
    out = RequisitionDetail.model_validate(requisition)
    out.allowed_transitions = workflow.allowed_transitions(requisition.status)
    return out


def _reload_detail(db: Session, requisition_id: int) -> RequisitionDetail:
    """Re-read after a write — same expire-first reasoning as the other modules."""
    db.expire_all()
    return _as_detail(_get_or_404(db, requisition_id, detail=True))


def _replace_items(db: Session, requisition: Requisition, items) -> None:
    """Swap the line items wholesale. Only called while the order is still pending."""
    for old in list(requisition.items):
        db.delete(old)
    for item in items:
        db.add(RequisitionItem(
            requisition_id=requisition.id,
            item_name=item.item_name,
            quantity=item.quantity,
            unit=item.unit,
        ))
    db.flush()


def _filtered(
    *,
    search: str | None,
    status_in: list[RequisitionStatus] | None,
    requested_by_id: int | None,
    date_from: date | None,
    date_to: date | None,
):
    stmt = select(Requisition).where(Requisition.is_deleted.is_(False))

    if search:
        pattern = f"%{search.strip()}%"
        # Match the order, its purpose, or any line item on it.
        item_ids = select(RequisitionItem.requisition_id).where(
            RequisitionItem.item_name.ilike(pattern)
        )
        requester_ids = select(User.id).where(User.full_name.ilike(pattern))
        stmt = stmt.where(
            or_(
                Requisition.req_no.ilike(pattern),
                Requisition.purpose.ilike(pattern),
                Requisition.requested_by_id.in_(requester_ids),
                Requisition.id.in_(item_ids),
            )
        )
    if status_in:
        stmt = stmt.where(Requisition.status.in_(status_in))
    if requested_by_id is not None:
        stmt = stmt.where(Requisition.requested_by_id == requested_by_id)

    start, end = range_bounds_utc(date_from, date_to)
    if start is not None:
        stmt = stmt.where(Requisition.requested_at >= start)
    if end is not None:
        stmt = stmt.where(Requisition.requested_at < end)
    return stmt


# --- list / summary ---


@router.get("", response_model=Page[RequisitionListItem])
def list_requisitions(
    db: Session = Depends(get_db),
    _: User = Depends(get_current_user),
    search: str | None = Query(None, description="Req no, purpose, item name, or requester"),
    status_in: list[RequisitionStatus] | None = Query(None, alias="status"),
    requested_by_id: int | None = None,
    date_from: date | None = Query(None, description="requested_at >= (Asia/Dhaka)"),
    date_to: date | None = Query(None, description="requested_at <= (Asia/Dhaka)"),
    sort: str = Query(
        "-requested_at",
        pattern="^-?(req_no|purpose|status|requested_at|requested_by|created_at)$",
    ),
    page: int = Query(1, ge=1),
    page_size: int = Query(25, ge=1, le=200),
) -> Page[RequisitionListItem]:
    stmt = _filtered(
        search=search,
        status_in=status_in,
        requested_by_id=requested_by_id,
        date_from=date_from,
        date_to=date_to,
    )
    total = db.scalar(select(func.count()).select_from(stmt.subquery())) or 0

    descending = sort.startswith("-")
    field = sort.lstrip("-")
    if field == "requested_by":
        order = (
            select(User.full_name)
            .where(User.id == Requisition.requested_by_id)
            .scalar_subquery()
        )
    else:
        order = getattr(Requisition, field)
    stmt = stmt.order_by(order.desc() if descending else order.asc(), Requisition.id.desc())

    rows = (
        db.scalars(stmt.offset((page - 1) * page_size).limit(page_size))
        .unique()
        .all()
    )
    return Page.build(
        [RequisitionListItem.model_validate(r) for r in rows], total, page, page_size
    )


@router.get("/summary", response_model=RequisitionSummary)
def requisition_summary(
    db: Session = Depends(get_db),
    _: User = Depends(get_current_user),
) -> RequisitionSummary:
    return RequisitionSummary(**workflow.summary(db))


@router.get("/status-summary", response_model=list[RequisitionStatusCount])
def status_summary(
    db: Session = Depends(get_db),
    _: User = Depends(get_current_user),
) -> list[RequisitionStatusCount]:
    """Counts per status for the filter chips."""
    rows = (
        select(Requisition.status, func.count())
        .where(Requisition.is_deleted.is_(False))
        .group_by(Requisition.status)
    )
    counts = {value: count for value, count in db.execute(rows).all()}
    return [
        RequisitionStatusCount(status=s, count=counts.get(s, 0)) for s in RequisitionStatus
    ]


# --- create / read / update / delete ---


@router.post("", response_model=RequisitionDetail, status_code=status.HTTP_201_CREATED)
def create_requisition(
    payload: RequisitionCreate,
    request: Request,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_write),
) -> RequisitionDetail:
    requisition = Requisition(
        req_no=next_number(db, "REQ"),
        purpose=payload.purpose,
        notes=payload.notes,
        requested_by_id=current_user.id,
        requested_at=utcnow(),
        created_by_id=current_user.id,
        status=RequisitionStatus.PENDING,
    )
    db.add(requisition)
    db.flush()

    _replace_items(db, requisition, payload.items)
    workflow.record_history(
        db,
        requisition,
        from_status=None,
        to_status=RequisitionStatus.PENDING,
        user_id=current_user.id,
        remarks="Requisition raised",
    )
    audit.record(
        db,
        user_id=current_user.id,
        action="create",
        entity_type="requisition",
        entity_id=requisition.id,
        payload={"req_no": requisition.req_no},
        ip_address=client_ip(request),
    )
    db.commit()
    return _reload_detail(db, requisition.id)


@router.get("/{requisition_id}", response_model=RequisitionDetail)
def get_requisition(
    requisition_id: int,
    db: Session = Depends(get_db),
    _: User = Depends(get_current_user),
) -> RequisitionDetail:
    return _reload_detail(db, requisition_id)


@router.patch("/{requisition_id}", response_model=RequisitionDetail)
def update_requisition(
    requisition_id: int,
    payload: RequisitionUpdate,
    request: Request,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_write),
) -> RequisitionDetail:
    requisition = _get_or_404(db, requisition_id, detail=True)
    if requisition.status != RequisitionStatus.PENDING:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"A '{requisition.status.value}' requisition can no longer be edited",
        )

    changes = payload.model_dump(exclude_unset=True)
    if not changes:
        return _reload_detail(db, requisition_id)

    if "items" in changes:
        _replace_items(db, requisition, payload.items)
        del changes["items"]
    for field, value in changes.items():
        setattr(requisition, field, value)

    audit.record(
        db,
        user_id=current_user.id,
        action="update",
        entity_type="requisition",
        entity_id=requisition.id,
        payload={k: str(v) for k, v in changes.items()},
        ip_address=client_ip(request),
    )
    db.commit()
    return _reload_detail(db, requisition_id)


@router.post("/{requisition_id}/status", response_model=RequisitionDetail)
def change_requisition_status(
    requisition_id: int,
    payload: RequisitionStatusChange,
    request: Request,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_admin),
) -> RequisitionDetail:
    """Approve, reject, re-open, or complete. Decision actions are admin-only."""
    requisition = _get_or_404(db, requisition_id, detail=True)

    workflow.change_status(db, requisition, payload, current_user)
    audit.record(
        db,
        user_id=current_user.id,
        action="status_change",
        entity_type="requisition",
        entity_id=requisition.id,
        payload={"to": payload.status.value},
        ip_address=client_ip(request),
    )
    db.commit()
    return _reload_detail(db, requisition_id)


@router.delete("/{requisition_id}", response_model=Message)
def delete_requisition(
    requisition_id: int,
    request: Request,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_admin),
) -> Message:
    """Soft delete. Completed orders are kept — they are the purchasing record."""
    requisition = _get_or_404(db, requisition_id)
    if requisition.status == RequisitionStatus.COMPLETED:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="A completed requisition cannot be deleted",
        )

    requisition.is_deleted = True
    requisition.deleted_at = utcnow()
    audit.record(
        db,
        user_id=current_user.id,
        action="delete",
        entity_type="requisition",
        entity_id=requisition.id,
        payload={"req_no": requisition.req_no},
        ip_address=client_ip(request),
    )
    db.commit()
    return Message(detail=f"Requisition {requisition.req_no} deleted")
