import datetime as dt
import uuid

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy import or_, select
from sqlalchemy.orm import Session

from flyway.api.deps import current_actor, get_db
from flyway.api.lists import IdsIn, Page, like, nz_range, page_params, paginate
from flyway.api.views import slip_detail, slip_row, transfer_row
from flyway.config import settings
from flyway.domain.actor import Actor
from flyway.domain.clock import utcnow
from flyway.models import PackingSlip, Transfer
from flyway.services.packing import (
    PackingSlipHeader, delete_packing_slips, save_packing_slip, set_packing_slip_completed,
)

router = APIRouter(prefix="/api/packing-slips")


class SlipIn(BaseModel):
    reference: str
    date: dt.datetime
    from_location: str
    from_rmp: str
    to_location: str
    to_rmp: str
    awb_number: str | None = None
    load_out_ids: list[uuid.UUID]


def _slip(db: Session, slip_id: uuid.UUID) -> PackingSlip:
    s = db.get(PackingSlip, slip_id)
    if s is None:
        raise HTTPException(status_code=404)
    return s


def _save(db: Session, actor: Actor, body: SlipIn, slip_id: uuid.UUID | None = None) -> dict:
    header = PackingSlipHeader(**body.model_dump(exclude={"load_out_ids"}))
    s = save_packing_slip(db, actor, header, body.load_out_ids, slip_id)
    db.commit()
    return slip_detail(db, s)


@router.get("/defaults")
def defaults():
    return {"from_location": settings.packing_from_location, "from_rmp": settings.packing_from_rmp}


@router.get("/candidates")
def candidates(client_id: uuid.UUID | None = None, from_date: dt.date | None = None, to_date: dt.date | None = None,
               keyword: str | None = None, to_rmp: str | None = None, slip_id: uuid.UUID | None = None,
               paged: bool = False, page_: Page = Depends(page_params), db: Session = Depends(get_db)):
    """可装箱的出库单：已完成、非本地、未装箱（编辑时加上本装箱单已有的）。"""
    q = select(Transfer).where(Transfer.is_load_out.is_(True), Transfer.is_local.is_(False),
                               Transfer.is_draft.is_(False))
    q = q.where(or_(Transfer.packing_slip_id.is_(None), Transfer.packing_slip_id == slip_id) if slip_id
                else Transfer.packing_slip_id.is_(None))
    if to_rmp:
        q = q.where(Transfer.to_rmp == to_rmp)
    if client_id:
        q = q.where(Transfer.client_id == client_id)
    start, end = nz_range(from_date, to_date)
    if start:
        q = q.where(Transfer.date >= start)
    if end:
        q = q.where(Transfer.date < end)
    if keyword:
        q = q.where(like(Transfer.code, keyword))
    if paged:
        rows, total = paginate(db, q, page_, {'date': Transfer.date}, '-date')
        return {'items': [transfer_row(t) for t in rows], 'total': total}
    return [transfer_row(t) for t in db.scalars(q.order_by(Transfer.date.desc()).limit(200)).all()]


@router.get("")
def list_slips(keyword: str | None = None, from_date: dt.date | None = None, to_date: dt.date | None = None,
               to_rmp: str | None = None, is_completed: bool | None = None, page: Page = Depends(page_params),
               db: Session = Depends(get_db)):
    q = select(PackingSlip)
    if keyword:
        q = q.where(or_(like(PackingSlip.reference, keyword), like(PackingSlip.awb_number, keyword)))
    start, end = nz_range(from_date, to_date)
    if start:
        q = q.where(PackingSlip.date >= start)
    if end:
        q = q.where(PackingSlip.date < end)
    if to_rmp:
        q = q.where(like(PackingSlip.to_rmp, to_rmp))
    if is_completed is not None:
        q = q.where(PackingSlip.completed.is_not(None) if is_completed else PackingSlip.completed.is_(None))
    rows, total = paginate(db, q, page, {"reference": PackingSlip.reference, "date": PackingSlip.date}, "-date")
    return {"items": [slip_row(s) for s in rows], "total": total}


@router.get("/{slip_id}")
def get_slip(slip_id: uuid.UUID, db: Session = Depends(get_db)):
    return slip_detail(db, _slip(db, slip_id))


@router.post("")
def create(body: SlipIn, db: Session = Depends(get_db), actor: Actor = Depends(current_actor)):
    return _save(db, actor, body)


@router.put("/{slip_id}")
def update(slip_id: uuid.UUID, body: SlipIn, db: Session = Depends(get_db), actor: Actor = Depends(current_actor)):
    return _save(db, actor, body, slip_id)


@router.post("/{slip_id}/complete")
def complete(slip_id: uuid.UUID, db: Session = Depends(get_db), actor: Actor = Depends(current_actor)):
    s = set_packing_slip_completed(db, actor, slip_id, True, utcnow())
    db.commit()
    return slip_detail(db, s)


@router.post("/delete", status_code=204)
def remove(body: IdsIn, db: Session = Depends(get_db), actor: Actor = Depends(current_actor)):
    delete_packing_slips(db, actor, body.ids)
    db.commit()
