import datetime as dt
import uuid

from fastapi import APIRouter, Depends, HTTPException, Query
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.routes_load_ins import batch_filter
from flyway.api.views import transfer_detail, transfer_row, units_of
from flyway.config import settings
from flyway.domain.actor import Actor
from flyway.domain.clock import utcnow
from flyway.models import Client, Product, ProductBatch, Transfer, TransferItem
from flyway.services.load_out import LoadOutHeader, LoadOutLine, delete_load_outs, save_load_out, set_load_out_draft
from flyway.services.packing import pack_load_outs, unpack_load_outs

router = APIRouter(prefix="/api/load-outs")


class LineIn(BaseModel):
    source_item_id: uuid.UUID
    quantity: int
    unit: int = 1
    reference: str | None = None
    id: uuid.UUID | None = None


class LoadOutIn(BaseModel):
    client_id: uuid.UUID
    code: str
    date: dt.datetime
    to_location: str | None = None
    to_rmp: str | None = None
    ed_identification: str | None = None
    export_to: str | None = None
    transport_company: str | None = None
    vehicle_registration: str | None = None
    condition_of_vehicle: str | None = None
    non_conforming_product: bool = False
    container_number: str | None = None
    seal_number: str | None = None
    can_number: str | None = None
    awb_number: str | None = None
    notes: str | None = None
    load_in_ids: list[uuid.UUID] = []
    lines: list[LineIn]


class PackIn(IdsIn):
    reference: str


def _load_out(db: Session, transfer_id: uuid.UUID) -> Transfer:
    t = db.get(Transfer, transfer_id)
    if t is None or not t.is_load_out or t.is_local:
        raise HTTPException(status_code=404)
    return t


def _save(db: Session, actor: Actor, body: LoadOutIn, transfer_id: uuid.UUID | None = None) -> dict:
    header = LoadOutHeader(**body.model_dump(exclude={"lines"}))
    t = save_load_out(db, actor, header, [LoadOutLine(**line.model_dump()) for line in body.lines], transfer_id)
    db.commit()
    return transfer_detail(db, t)


def sources_query(db: Session, client_id: uuid.UUID, keyword: str | None, load_in_ids: list[uuid.UUID],
                  transfer_id: uuid.UUID | None) -> list[dict]:
    """可选来源：该客户已完成入库类明细中剩余 > 0 的；编辑时加上本单已扣减的数量（Review Focus 3）。"""
    own: dict[uuid.UUID, int] = {}
    if transfer_id:
        t = db.get(Transfer, transfer_id)
        if t is not None and not t.is_draft:
            for i in t.items:
                own[i.related_transfer_item_id] = own.get(i.related_transfer_item_id, 0) + i.quantity * i.unit
    q = (select(TransferItem).join(Transfer).join(ProductBatch).join(Product)
         .where(Transfer.client_id == client_id, Transfer.is_load_out.is_(False), Transfer.is_draft.is_(False))
         .where(or_(TransferItem.remaining_stock > 0, TransferItem.id.in_(list(own) or [uuid.uuid4()]))))
    if keyword:
        q = q.where(or_(like(Product.title, keyword), like(ProductBatch.number, keyword), like(Transfer.code, keyword)))
    if load_in_ids:
        q = q.where(Transfer.id.in_(load_in_ids))
    items = db.scalars(q.order_by(Transfer.date.desc(), Product.title).limit(300)).all()
    return [{"id": i.id, "load_in_id": i.transfer_id, "load_in_code": i.transfer.code, "date": i.transfer.date,
             "product_title": i.batch.product.title, "batch_number": i.batch.number,
             "used_by_date": i.batch.used_by_date, "remaining_stock": i.remaining_stock,
             "available": i.remaining_stock + own.get(i.id, 0), "units": units_of(i.batch.product),
             "default_unit": i.batch.product.default_unit} for i in items]


@router.get("/defaults")
def defaults():
    return {"transport_company": settings.load_out_transport_company,
            "vehicle_registration": settings.load_out_vehicle_registration}


@router.get("/sources")
def sources(client_id: uuid.UUID, keyword: str | None = None, load_in_ids: list[uuid.UUID] = Query([]),
            transfer_id: uuid.UUID | None = None, db: Session = Depends(get_db)):
    return sources_query(db, client_id, keyword, load_in_ids, transfer_id)


@router.get("")
def list_load_outs(keyword: str | None = None, client_id: uuid.UUID | None = None,
                   from_date: dt.date | None = None, to_date: dt.date | None = None, to_rmp: str | None = None,
                   batch: str | None = None, is_draft: bool | None = None, is_packed: bool | None = None,
                   page: Page = Depends(page_params), db: Session = Depends(get_db)):
    q = select(Transfer).join(Client).where(Transfer.is_load_out.is_(True), Transfer.is_local.is_(False))
    if keyword:
        q = q.where(like(Transfer.code, keyword))
    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 to_rmp:
        q = q.where(like(Transfer.to_rmp, to_rmp))
    if batch:
        q = batch_filter(q, batch)
    if is_draft is not None:
        q = q.where(Transfer.is_draft.is_(is_draft))
    if is_packed is not None:
        q = q.where(Transfer.packing_slip_id.is_not(None) if is_packed else Transfer.packing_slip_id.is_(None))
    rows, total = paginate(db, q, page, {"code": Transfer.code, "date": Transfer.date, "client": Client.name}, "-date")
    return {"items": [transfer_row(t) for t in rows], "total": total}


@router.get("/{transfer_id}")
def get_load_out(transfer_id: uuid.UUID, db: Session = Depends(get_db)):
    return transfer_detail(db, _load_out(db, transfer_id))


@router.post("")
def create(body: LoadOutIn, db: Session = Depends(get_db), actor: Actor = Depends(current_actor)):
    return _save(db, actor, body)


@router.put("/{transfer_id}")
def update(transfer_id: uuid.UUID, body: LoadOutIn, db: Session = Depends(get_db),
           actor: Actor = Depends(current_actor)):
    return _save(db, actor, body, transfer_id)


@router.post("/{transfer_id}/complete")
def complete(transfer_id: uuid.UUID, db: Session = Depends(get_db), actor: Actor = Depends(current_actor)):
    t = set_load_out_draft(db, actor, transfer_id, False)
    db.commit()
    return transfer_detail(db, t)


@router.post("/delete", status_code=204)
def remove(body: IdsIn, db: Session = Depends(get_db), actor: Actor = Depends(current_actor)):
    delete_load_outs(db, actor, body.ids)
    db.commit()


@router.post("/pack", status_code=204)
def pack(body: PackIn, db: Session = Depends(get_db), actor: Actor = Depends(current_actor)):
    pack_load_outs(db, actor, body.ids, body.reference, utcnow())
    db.commit()


@router.post("/unpack", status_code=204)
def unpack(body: IdsIn, db: Session = Depends(get_db), actor: Actor = Depends(current_actor)):
    unpack_load_outs(db, actor, body.ids)
    db.commit()
