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

from app.api.deps import AuthSession, require_client_module_access
from app.db.session import get_db
from app.models.product_costing import (
    ProductCostBagInput,
    ProductCostBaseInput,
    ProductCostClientInput,
    ProductCostFreightInput,
    ProductCostItem,
    ProductCostProcessInput,
)
from app.schemas.product_costing import (
    ProductCostInputsRead,
    ProductCostInputsUpdate,
    ProductCostItemCreate,
    ProductCostItemRead,
    ProductCostItemUpdate,
    ProductCostRecalculateAllRead,
)
from app.services.product_costing_service import (
    BAG_INPUTS,
    FREIGHT_INPUTS,
    PROCESS_NAMES,
    recalculate_all_product_cost_items,
    recalculate_product_cost_item,
    serialize_product_cost_item,
)

router = APIRouter(prefix="/api/product-costing", tags=["product-costing"])


def _load_item(db: Session, tenant_id: str, item_id: int) -> ProductCostItem | None:
    return db.scalar(select(ProductCostItem).where(ProductCostItem.id == item_id, ProductCostItem.tenant_id == tenant_id))


def _ensure_inputs(db: Session, tenant_id: str) -> ProductCostBaseInput:
    base = db.scalar(select(ProductCostBaseInput).where(ProductCostBaseInput.tenant_id == tenant_id))
    if base is None:
        base = ProductCostBaseInput(tenant_id=tenant_id)
        db.add(base)
        db.flush()
    for process_name in PROCESS_NAMES:
        if db.scalar(select(ProductCostProcessInput.id).where(ProductCostProcessInput.tenant_id == tenant_id, ProductCostProcessInput.process_name == process_name)) is None:
            db.add(ProductCostProcessInput(tenant_id=tenant_id, process_name=process_name, cost_per_kg=0.0))
    for key, label in BAG_INPUTS.items():
        if db.scalar(select(ProductCostBagInput.id).where(ProductCostBagInput.tenant_id == tenant_id, ProductCostBagInput.input_key == key)) is None:
            db.add(ProductCostBagInput(tenant_id=tenant_id, input_key=key, label=label, cost=0.0))
    for key, label in FREIGHT_INPUTS.items():
        if db.scalar(select(ProductCostFreightInput.id).where(ProductCostFreightInput.tenant_id == tenant_id, ProductCostFreightInput.input_key == key)) is None:
            db.add(ProductCostFreightInput(tenant_id=tenant_id, input_key=key, label=label, cost=0.0))
    db.flush()
    return base


def _serialize_inputs(db: Session, tenant_id: str) -> dict:
    base = _ensure_inputs(db, tenant_id)
    return {
        "base": {
            "grading_per_tonne": base.grading_per_tonne,
            "grading_per_kg": base.grading_per_kg,
            "cracking_per_tonne": base.cracking_per_tonne,
            "cracking_per_kg": base.cracking_per_kg,
        },
        "processes": [
            {"key": row.process_name, "label": row.process_name, "cost": row.cost_per_kg}
            for row in db.scalars(select(ProductCostProcessInput).where(ProductCostProcessInput.tenant_id == tenant_id).order_by(ProductCostProcessInput.process_name)).all()
        ],
        "clients": [
            {
                "client_category": row.client_category,
                "distributor_margin": row.distributor_margin,
                "wholesale_margin": row.wholesale_margin,
            }
            for row in db.scalars(select(ProductCostClientInput).where(ProductCostClientInput.tenant_id == tenant_id).order_by(ProductCostClientInput.client_category)).all()
        ],
        "bags": [
            {"key": row.input_key, "label": row.label, "cost": row.cost}
            for row in db.scalars(select(ProductCostBagInput).where(ProductCostBagInput.tenant_id == tenant_id).order_by(ProductCostBagInput.input_key)).all()
        ],
        "freight": [
            {"key": row.input_key, "label": row.label, "cost": row.cost}
            for row in db.scalars(select(ProductCostFreightInput).where(ProductCostFreightInput.tenant_id == tenant_id).order_by(ProductCostFreightInput.input_key)).all()
        ],
    }


@router.get("/items", response_model=list[ProductCostItemRead])
def list_product_cost_items(
    q: str | None = Query(default=None),
    client_category: str | None = Query(default=None),
    limit: int = Query(default=250, ge=1, le=1000),
    session: AuthSession = Depends(require_client_module_access("products")),
    db: Session = Depends(get_db),
):
    statement = select(ProductCostItem).where(ProductCostItem.tenant_id == session.tenant_id)
    if client_category:
        statement = statement.where(ProductCostItem.client_category == client_category)
    if q:
        term = f"%{q}%"
        statement = statement.where(
            or_(
                ProductCostItem.client_category.ilike(term),
                ProductCostItem.item_id.ilike(term),
                ProductCostItem.product_name.ilike(term),
                ProductCostItem.mix_product_name.ilike(term),
            )
        )
    items = db.scalars(statement.order_by(ProductCostItem.client_category, ProductCostItem.product_name).limit(limit)).all()
    return [serialize_product_cost_item(item) for item in items]


@router.post("/items", response_model=ProductCostItemRead, status_code=status.HTTP_201_CREATED)
def create_product_cost_item(
    payload: ProductCostItemCreate,
    session: AuthSession = Depends(require_client_module_access("products", "edit")),
    db: Session = Depends(get_db),
):
    item = ProductCostItem(tenant_id=session.tenant_id or "default", **payload.model_dump())
    db.add(item)
    db.flush()
    recalculate_product_cost_item(db, item)
    db.commit()
    db.refresh(item)
    return serialize_product_cost_item(item)


@router.get("/items/{item_id}", response_model=ProductCostItemRead)
def get_product_cost_item(
    item_id: int,
    session: AuthSession = Depends(require_client_module_access("products")),
    db: Session = Depends(get_db),
):
    item = _load_item(db, session.tenant_id or "default", item_id)
    if item is None:
        raise HTTPException(status_code=404, detail="Product cost item not found")
    return serialize_product_cost_item(item)


@router.patch("/items/{item_id}", response_model=ProductCostItemRead)
def update_product_cost_item(
    item_id: int,
    payload: ProductCostItemUpdate,
    session: AuthSession = Depends(require_client_module_access("products", "edit")),
    db: Session = Depends(get_db),
):
    item = _load_item(db, session.tenant_id or "default", item_id)
    if item is None:
        raise HTTPException(status_code=404, detail="Product cost item not found")
    for field, value in payload.model_dump(exclude_unset=True).items():
        setattr(item, field, value)
    recalculate_product_cost_item(db, item)
    db.commit()
    db.refresh(item)
    return serialize_product_cost_item(item)


@router.post("/items/{item_id}/recalculate", response_model=ProductCostItemRead)
def recalculate_one(
    item_id: int,
    session: AuthSession = Depends(require_client_module_access("products", "edit")),
    db: Session = Depends(get_db),
):
    item = _load_item(db, session.tenant_id or "default", item_id)
    if item is None:
        raise HTTPException(status_code=404, detail="Product cost item not found")
    recalculate_product_cost_item(db, item)
    db.commit()
    db.refresh(item)
    return serialize_product_cost_item(item)


@router.post("/recalculate-all", response_model=ProductCostRecalculateAllRead)
def recalculate_all(
    session: AuthSession = Depends(require_client_module_access("products", "edit")),
    db: Session = Depends(get_db),
):
    count = recalculate_all_product_cost_items(db, session.tenant_id or "default")
    db.commit()
    return {"recalculated": count}


@router.get("/inputs", response_model=ProductCostInputsRead)
def get_product_cost_inputs(
    session: AuthSession = Depends(require_client_module_access("products")),
    db: Session = Depends(get_db),
):
    return _serialize_inputs(db, session.tenant_id or "default")


@router.patch("/inputs", response_model=ProductCostInputsRead)
def update_product_cost_inputs(
    payload: ProductCostInputsUpdate,
    session: AuthSession = Depends(require_client_module_access("products", "edit")),
    db: Session = Depends(get_db),
):
    tenant_id = session.tenant_id or "default"
    base = _ensure_inputs(db, tenant_id)
    if payload.base is not None:
        for field, value in payload.base.model_dump().items():
            setattr(base, field, value)

    if payload.processes is not None:
        existing = {row.process_name: row for row in db.scalars(select(ProductCostProcessInput).where(ProductCostProcessInput.tenant_id == tenant_id)).all()}
        for row in payload.processes:
            target = existing.get(row.key)
            if target is None:
                db.add(ProductCostProcessInput(tenant_id=tenant_id, process_name=row.key, cost_per_kg=row.cost))
            else:
                target.cost_per_kg = row.cost

    if payload.clients is not None:
        existing = {row.client_category: row for row in db.scalars(select(ProductCostClientInput).where(ProductCostClientInput.tenant_id == tenant_id)).all()}
        for row in payload.clients:
            target = existing.get(row.client_category)
            if target is None:
                db.add(ProductCostClientInput(tenant_id=tenant_id, client_category=row.client_category, distributor_margin=row.distributor_margin, wholesale_margin=row.wholesale_margin))
            else:
                target.distributor_margin = row.distributor_margin
                target.wholesale_margin = row.wholesale_margin

    if payload.bags is not None:
        existing = {row.input_key: row for row in db.scalars(select(ProductCostBagInput).where(ProductCostBagInput.tenant_id == tenant_id)).all()}
        for row in payload.bags:
            target = existing.get(row.key)
            if target is None:
                db.add(ProductCostBagInput(tenant_id=tenant_id, input_key=row.key, label=row.label, cost=row.cost))
            else:
                target.label = row.label
                target.cost = row.cost

    if payload.freight is not None:
        existing = {row.input_key: row for row in db.scalars(select(ProductCostFreightInput).where(ProductCostFreightInput.tenant_id == tenant_id)).all()}
        for row in payload.freight:
            target = existing.get(row.key)
            if target is None:
                db.add(ProductCostFreightInput(tenant_id=tenant_id, input_key=row.key, label=row.label, cost=row.cost))
            else:
                target.label = row.label
                target.cost = row.cost

    db.flush()
    recalculate_all_product_cost_items(db, tenant_id)
    db.commit()
    return _serialize_inputs(db, tenant_id)
