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

from app.api.deps import AuthSession, require_client_module_access
from app.db.session import get_db
from app.models.raw_material import RawMaterial, RawMaterialPriceVersion
from app.schemas.raw_material import (
    RawMaterialCreate,
    RawMaterialPriceVersionCreate,
    RawMaterialPriceVersionRead,
    RawMaterialRead,
    RawMaterialUpdate,
)
from app.services.costing_engine import calculate_raw_material_cost, serialize_raw_material

router = APIRouter(prefix="/api/raw-materials", tags=["raw-materials"])


def _serialize_price(material: RawMaterial, price: RawMaterialPriceVersion) -> dict:
    price_comp = calculate_raw_material_cost(material, price)
    return {
        "id": price.id,
        "market_value": price.market_value,
        "waste_percentage": price.waste_percentage,
        "effective_date": price.effective_date,
        "status": price.status,
        "notes": price.notes,
        "created_at": price.created_at,
        "loss_cost": price_comp.loss_cost,
        "cost_per_unit": price_comp.cost_per_unit,
        "cost_per_kg": price_comp.cost_per_kg,
    }


@router.get("", response_model=list[RawMaterialRead])
def list_raw_materials(
    limit: int = Query(default=100, ge=1, le=200),
    session: AuthSession = Depends(require_client_module_access("raw_materials")),
    db: Session = Depends(get_db),
):
    materials = db.scalars(
        select(RawMaterial)
        .where(RawMaterial.tenant_id == session.tenant_id)
        .options(selectinload(RawMaterial.price_versions))
        .order_by(RawMaterial.name)
        .limit(limit)
    ).all()
    return [serialize_raw_material(material) for material in materials]


@router.post("", response_model=RawMaterialRead, status_code=status.HTTP_201_CREATED)
def create_raw_material(payload: RawMaterialCreate, session: AuthSession = Depends(require_client_module_access("raw_materials", "edit")), db: Session = Depends(get_db)):
    material = RawMaterial(
        tenant_id=session.tenant_id,
        name=payload.name,
        supplier=payload.supplier,
        unit_of_measure=payload.unit_of_measure,
        kg_per_unit=payload.kg_per_unit,
        status=payload.status,
        notes=payload.notes,
    )
    material.price_versions.append(
        RawMaterialPriceVersion(
            tenant_id=session.tenant_id,
            market_value=payload.initial_price.market_value,
            waste_percentage=payload.initial_price.waste_percentage,
            effective_date=payload.initial_price.effective_date,
            status=payload.initial_price.status,
            notes=payload.initial_price.notes,
        )
    )
    db.add(material)
    db.commit()
    db.refresh(material)
    return serialize_raw_material(material)


@router.get("/{raw_material_id}", response_model=RawMaterialRead)
def get_raw_material(raw_material_id: int, session: AuthSession = Depends(require_client_module_access("raw_materials")), db: Session = Depends(get_db)):
    material = db.scalar(
        select(RawMaterial)
        .where(RawMaterial.id == raw_material_id, RawMaterial.tenant_id == session.tenant_id)
        .options(selectinload(RawMaterial.price_versions))
    )
    if material is None:
        raise HTTPException(status_code=404, detail="Raw material not found")
    return serialize_raw_material(material)


@router.patch("/{raw_material_id}", response_model=RawMaterialRead)
def update_raw_material(
    raw_material_id: int,
    payload: RawMaterialUpdate,
    session: AuthSession = Depends(require_client_module_access("raw_materials", "edit")),
    db: Session = Depends(get_db),
):
    material = db.scalar(
        select(RawMaterial)
        .where(RawMaterial.id == raw_material_id, RawMaterial.tenant_id == session.tenant_id)
        .options(selectinload(RawMaterial.price_versions))
    )
    if material is None:
        raise HTTPException(status_code=404, detail="Raw material not found")
    for field, value in payload.model_dump(exclude_unset=True).items():
        setattr(material, field, value)
    db.commit()
    db.refresh(material)
    return serialize_raw_material(material)


@router.post("/{raw_material_id}/prices", response_model=RawMaterialPriceVersionRead, status_code=status.HTTP_201_CREATED)
def add_price_version(
    raw_material_id: int,
    payload: RawMaterialPriceVersionCreate,
    session: AuthSession = Depends(require_client_module_access("raw_materials", "edit")),
    db: Session = Depends(get_db),
):
    material = db.scalar(select(RawMaterial).where(RawMaterial.id == raw_material_id, RawMaterial.tenant_id == session.tenant_id))
    if material is None:
        raise HTTPException(status_code=404, detail="Raw material not found")
    price = RawMaterialPriceVersion(
        tenant_id=session.tenant_id,
        raw_material_id=raw_material_id,
        market_value=payload.market_value,
        waste_percentage=payload.waste_percentage,
        effective_date=payload.effective_date,
        status=payload.status,
        notes=payload.notes,
    )
    db.add(price)
    db.commit()
    db.refresh(price)
    return _serialize_price(material, price)


@router.get("/{raw_material_id}/price-history", response_model=list[RawMaterialPriceVersionRead])
def get_price_history(
    raw_material_id: int,
    limit: int = Query(default=100, ge=1, le=200),
    session: AuthSession = Depends(require_client_module_access("raw_materials")),
    db: Session = Depends(get_db),
):
    material = db.scalar(select(RawMaterial).where(RawMaterial.id == raw_material_id, RawMaterial.tenant_id == session.tenant_id))
    if material is None:
        raise HTTPException(status_code=404, detail="Raw material not found")
    prices = db.scalars(
        select(RawMaterialPriceVersion)
        .where(
            RawMaterialPriceVersion.raw_material_id == raw_material_id,
            RawMaterialPriceVersion.tenant_id == session.tenant_id,
        )
        .order_by(RawMaterialPriceVersion.effective_date.desc())
        .limit(limit)
    ).all()
    items = []
    for price in prices:
        items.append(_serialize_price(material, price))
    return items
