from __future__ import annotations

from datetime import date

from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status
from sqlalchemy import 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.throughput import ProductionThroughput, ThroughputProduct
from app.schemas.throughput import (
    ThroughputDeleteAllResult,
    ThroughputEntryCreate,
    ThroughputEntryRead,
    ThroughputEntryUpdate,
    ThroughputImportResult,
    ThroughputProductCreate,
    ThroughputProductRead,
    ThroughputProductUpdate,
)
from app.services.throughput_service import (
    calculate_kg,
    import_entries_from_file,
    normalise_staff_name,
    serialize_entry,
)

# Uploaded files larger than this are rejected before we read them into memory.
_MAX_IMPORT_BYTES = 10 * 1024 * 1024  # 10 MB

router = APIRouter(prefix="/api/throughput", tags=["operations-throughput"])

MODULE_KEY = "operations_throughput"


@router.get("/products", response_model=list[ThroughputProductRead])
def list_products(
    include_inactive: bool = Query(default=False),
    session: AuthSession = Depends(require_client_module_access(MODULE_KEY)),
    db: Session = Depends(get_db),
):
    stmt = select(ThroughputProduct).where(ThroughputProduct.tenant_id == session.tenant_id)
    if not include_inactive:
        stmt = stmt.where(ThroughputProduct.active.is_(True))
    stmt = stmt.order_by(ThroughputProduct.name)
    return db.scalars(stmt).all()


@router.post("/products", response_model=ThroughputProductRead, status_code=status.HTTP_201_CREATED)
def create_product(
    payload: ThroughputProductCreate,
    session: AuthSession = Depends(require_client_module_access(MODULE_KEY, "edit")),
    db: Session = Depends(get_db),
):
    if payload.item_id:
        existing = db.scalar(
            select(ThroughputProduct).where(
                ThroughputProduct.tenant_id == session.tenant_id,
                ThroughputProduct.item_id == payload.item_id,
            )
        )
        if existing is not None:
            raise HTTPException(status_code=409, detail="A product with this item_id already exists")

    product = ThroughputProduct(
        tenant_id=session.tenant_id,
        item_id=payload.item_id,
        name=payload.name,
        default_bag_size=payload.default_bag_size,
        is_bulka_default=payload.is_bulka_default,
        active=payload.active,
        is_stock_item=payload.is_stock_item,
        notes=payload.notes,
    )
    db.add(product)
    db.commit()
    db.refresh(product)
    return product


@router.patch("/products/{product_id}", response_model=ThroughputProductRead)
def update_product(
    product_id: int,
    payload: ThroughputProductUpdate,
    session: AuthSession = Depends(require_client_module_access(MODULE_KEY, "edit")),
    db: Session = Depends(get_db),
):
    product = db.scalar(
        select(ThroughputProduct).where(
            ThroughputProduct.id == product_id,
            ThroughputProduct.tenant_id == session.tenant_id,
        )
    )
    if product is None:
        raise HTTPException(status_code=404, detail="Product not found")
    for field, value in payload.model_dump(exclude_unset=True).items():
        setattr(product, field, value)
    db.commit()
    db.refresh(product)
    return product


@router.get("/entries", response_model=list[ThroughputEntryRead])
def list_entries(
    date_from: date | None = Query(default=None),
    date_to: date | None = Query(default=None),
    product_id: int | None = Query(default=None),
    staff_name: str | None = Query(default=None),
    quantity_type: str | None = Query(default=None),
    limit: int = Query(default=200, ge=1, le=1000),
    session: AuthSession = Depends(require_client_module_access(MODULE_KEY)),
    db: Session = Depends(get_db),
):
    stmt = select(ProductionThroughput).where(ProductionThroughput.tenant_id == session.tenant_id)
    if date_from is not None:
        stmt = stmt.where(ProductionThroughput.production_date >= date_from)
    if date_to is not None:
        stmt = stmt.where(ProductionThroughput.production_date <= date_to)
    if product_id is not None:
        stmt = stmt.where(ProductionThroughput.product_id == product_id)
    if staff_name:
        stmt = stmt.where(ProductionThroughput.staff_name == staff_name.strip())
    if quantity_type in {"bags", "kg"}:
        stmt = stmt.where(ProductionThroughput.quantity_type == quantity_type)
    stmt = stmt.order_by(ProductionThroughput.production_date.desc(), ProductionThroughput.id.desc()).limit(limit)
    return [serialize_entry(entry) for entry in db.scalars(stmt).all()]


@router.post("/entries", response_model=ThroughputEntryRead, status_code=status.HTTP_201_CREATED)
def create_entry(
    payload: ThroughputEntryCreate,
    session: AuthSession = Depends(require_client_module_access(MODULE_KEY, "edit")),
    db: Session = Depends(get_db),
):
    product = None
    if payload.product_id is not None:
        product = db.scalar(
            select(ThroughputProduct).where(
                ThroughputProduct.id == payload.product_id,
                ThroughputProduct.tenant_id == session.tenant_id,
            )
        )
        if product is None:
            raise HTTPException(status_code=400, detail="product_id does not match an existing product")

    snapshot = payload.product_name_snapshot or (product.name if product else None)
    if not snapshot:
        raise HTTPException(status_code=400, detail="product_name_snapshot or product_id is required")

    bag_size = payload.bag_size
    if bag_size is None and product is not None:
        bag_size = product.default_bag_size

    if payload.quantity_type == "bags" and (bag_size is None or bag_size <= 0):
        raise HTTPException(status_code=400, detail="bag_size is required when quantity_type is 'bags'")

    calculated = calculate_kg(payload.quantity, payload.quantity_type, bag_size)

    entry = ProductionThroughput(
        tenant_id=session.tenant_id,
        production_date=payload.production_date,
        product_id=product.id if product else None,
        product_name_snapshot=snapshot,
        bag_size=bag_size,
        scales_checked=payload.scales_checked,
        label_correct=payload.label_correct,
        bag_sealed=payload.bag_sealed,
        pallet_good_condition=payload.pallet_good_condition,
        for_order=payload.for_order,
        for_stock=payload.for_stock,
        job_number=payload.job_number,
        stock_quantity=payload.stock_quantity if payload.for_stock else None,
        sample_box_no=payload.sample_box_no,
        test_weight_1=payload.test_weight_1,
        test_weight_2=payload.test_weight_2,
        test_weight_3=payload.test_weight_3,
        test_weight_4=payload.test_weight_4,
        test_weight_5=payload.test_weight_5,
        quantity=payload.quantity,
        quantity_type=payload.quantity_type,
        calculated_kg=calculated,
        staff_name=normalise_staff_name(payload.staff_name),
        notes=payload.notes,
        created_by=session.email,
    )
    db.add(entry)
    db.commit()
    db.refresh(entry)
    return serialize_entry(entry)


@router.post("/import", response_model=ThroughputImportResult)
def import_entries(
    file: UploadFile = File(...),
    session: AuthSession = Depends(require_client_module_access(MODULE_KEY, "edit")),
    db: Session = Depends(get_db),
):
    content = file.file.read()
    if not content:
        raise HTTPException(status_code=400, detail="The uploaded file is empty.")
    if len(content) > _MAX_IMPORT_BYTES:
        raise HTTPException(status_code=413, detail="File is too large. Keep uploads under 10 MB.")

    try:
        result = import_entries_from_file(
            db,
            filename=file.filename or "upload.csv",
            content=content,
            tenant_id=session.tenant_id,
            created_by=session.email,
        )
    except ValueError as exc:
        db.rollback()
        raise HTTPException(status_code=400, detail=str(exc)) from exc

    return result


@router.delete("/entries", response_model=ThroughputDeleteAllResult)
def delete_all_entries(
    # Clearing the log is part of correcting a bad import, so it sits at the same
    # "edit" level as deleting a single entry. It is scoped to the caller's
    # tenant, so one client can never wipe another's data.
    session: AuthSession = Depends(require_client_module_access(MODULE_KEY, "edit")),
    db: Session = Depends(get_db),
):
    deleted = db.query(ProductionThroughput).filter(
        ProductionThroughput.tenant_id == session.tenant_id
    ).delete(synchronize_session=False)
    db.commit()
    return ThroughputDeleteAllResult(entries_deleted=deleted)


@router.get("/entries/{entry_id}", response_model=ThroughputEntryRead)
def get_entry(
    entry_id: int,
    session: AuthSession = Depends(require_client_module_access(MODULE_KEY)),
    db: Session = Depends(get_db),
):
    entry = db.scalar(
        select(ProductionThroughput).where(
            ProductionThroughput.id == entry_id,
            ProductionThroughput.tenant_id == session.tenant_id,
        )
    )
    if entry is None:
        raise HTTPException(status_code=404, detail="Entry not found")
    return serialize_entry(entry)


@router.patch("/entries/{entry_id}", response_model=ThroughputEntryRead)
def update_entry(
    entry_id: int,
    payload: ThroughputEntryUpdate,
    session: AuthSession = Depends(require_client_module_access(MODULE_KEY, "edit")),
    db: Session = Depends(get_db),
):
    entry = db.scalar(
        select(ProductionThroughput).where(
            ProductionThroughput.id == entry_id,
            ProductionThroughput.tenant_id == session.tenant_id,
        )
    )
    if entry is None:
        raise HTTPException(status_code=404, detail="Entry not found")

    data = payload.model_dump(exclude_unset=True)
    if "staff_name" in data:
        data["staff_name"] = normalise_staff_name(data["staff_name"])
    for field, value in data.items():
        setattr(entry, field, value)

    entry.calculated_kg = calculate_kg(entry.quantity, entry.quantity_type, entry.bag_size)

    db.commit()
    db.refresh(entry)
    return serialize_entry(entry)


@router.delete("/entries/{entry_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_entry(
    entry_id: int,
    # Correcting a mistaken run is part of day-to-day operating, so deleting an
    # entry sits at the same "edit" level as adding/editing one. (No throughput
    # role is granted "manage", so requiring it here would 403 everyone.)
    session: AuthSession = Depends(require_client_module_access(MODULE_KEY, "edit")),
    db: Session = Depends(get_db),
):
    entry = db.scalar(
        select(ProductionThroughput).where(
            ProductionThroughput.id == entry_id,
            ProductionThroughput.tenant_id == session.tenant_id,
        )
    )
    if entry is None:
        raise HTTPException(status_code=404, detail="Entry not found")
    db.delete(entry)
    db.commit()
    return None
