"""Van inventory API router."""

from __future__ import annotations

from typing import TYPE_CHECKING

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.orm import selectinload

from python.orm.van_inventory.models import Item, Meal, MealIngredient

if TYPE_CHECKING:
    from python.van_inventory.dependencies import DbSession


# --- Schemas ---


class ItemCreate(BaseModel):
    """Schema for creating an item."""

    name: str
    quantity: float = Field(default=0, ge=0)
    unit: str
    category: str | None = None


class ItemUpdate(BaseModel):
    """Schema for updating an item."""

    name: str | None = None
    quantity: float | None = Field(default=None, ge=0)
    unit: str | None = None
    category: str | None = None


class ItemResponse(BaseModel):
    """Schema for item response."""

    id: int
    name: str
    quantity: float
    unit: str
    category: str | None

    model_config = {"from_attributes": True}


class IngredientCreate(BaseModel):
    """Schema for adding an ingredient to a meal."""

    item_id: int
    quantity_needed: float = Field(gt=0)


class MealCreate(BaseModel):
    """Schema for creating a meal."""

    name: str
    instructions: str | None = None
    ingredients: list[IngredientCreate] = []


class MealUpdate(BaseModel):
    """Schema for updating a meal."""

    name: str | None = None
    instructions: str | None = None


class IngredientResponse(BaseModel):
    """Schema for ingredient response."""

    item_id: int
    item_name: str
    quantity_needed: float
    unit: str

    model_config = {"from_attributes": True}


class MealResponse(BaseModel):
    """Schema for meal response."""

    id: int
    name: str
    instructions: str | None
    ingredients: list[IngredientResponse] = []

    model_config = {"from_attributes": True}

    @classmethod
    def from_meal(cls, meal: Meal) -> MealResponse:
        """Build a MealResponse from an ORM Meal with loaded ingredients."""
        return cls(
            id=meal.id,
            name=meal.name,
            instructions=meal.instructions,
            ingredients=[
                IngredientResponse(
                    item_id=mi.item_id,
                    item_name=mi.item.name,
                    quantity_needed=mi.quantity_needed,
                    unit=mi.item.unit,
                )
                for mi in meal.ingredients
            ],
        )


class ShoppingItem(BaseModel):
    """An item needed for a meal that is short on stock."""

    item_name: str
    unit: str
    needed: float
    have: float
    short: float


class MealAvailability(BaseModel):
    """Availability status for a meal."""

    meal_id: int
    meal_name: str
    can_make: bool
    missing: list[ShoppingItem] = []


# --- Routes ---

router = APIRouter(prefix="/api", tags=["van_inventory"])


# Items


@router.post("/items", response_model=ItemResponse)
def create_item(item: ItemCreate, db: DbSession) -> Item:
    """Create a new inventory item."""
    db_item = Item(**item.model_dump())
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item


@router.get("/items", response_model=list[ItemResponse])
def list_items(db: DbSession) -> list[Item]:
    """List all inventory items."""
    return list(db.scalars(select(Item).order_by(Item.name)).all())


@router.get("/items/{item_id}", response_model=ItemResponse)
def get_item(item_id: int, db: DbSession) -> Item:
    """Get an item by ID."""
    item = db.get(Item, item_id)
    if not item:
        raise HTTPException(status_code=404, detail="Item not found")
    return item


@router.patch("/items/{item_id}", response_model=ItemResponse)
def update_item(item_id: int, item: ItemUpdate, db: DbSession) -> Item:
    """Update an item by ID."""
    db_item = db.get(Item, item_id)
    if not db_item:
        raise HTTPException(status_code=404, detail="Item not found")
    for key, value in item.model_dump(exclude_unset=True).items():
        setattr(db_item, key, value)
    db.commit()
    db.refresh(db_item)
    return db_item


@router.delete("/items/{item_id}")
def delete_item(item_id: int, db: DbSession) -> dict[str, bool]:
    """Delete an item by ID."""
    item = db.get(Item, item_id)
    if not item:
        raise HTTPException(status_code=404, detail="Item not found")
    db.delete(item)
    db.commit()
    return {"deleted": True}


# Meals


@router.post("/meals", response_model=MealResponse)
def create_meal(meal: MealCreate, db: DbSession) -> MealResponse:
    """Create a new meal with optional ingredients."""
    for ing in meal.ingredients:
        if not db.get(Item, ing.item_id):
            raise HTTPException(status_code=422, detail=f"Item {ing.item_id} not found")
    db_meal = Meal(name=meal.name, instructions=meal.instructions)
    db.add(db_meal)
    db.flush()
    for ing in meal.ingredients:
        db.add(MealIngredient(meal_id=db_meal.id, item_id=ing.item_id, quantity_needed=ing.quantity_needed))
    db.commit()
    db_meal = db.scalar(
        select(Meal)
        .where(Meal.id == db_meal.id)
        .options(selectinload(Meal.ingredients).selectinload(MealIngredient.item))
    )
    return MealResponse.from_meal(db_meal)


@router.get("/meals", response_model=list[MealResponse])
def list_meals(db: DbSession) -> list[MealResponse]:
    """List all meals with ingredients."""
    meals = list(
        db.scalars(
            select(Meal).options(selectinload(Meal.ingredients).selectinload(MealIngredient.item)).order_by(Meal.name)
        ).all()
    )
    return [MealResponse.from_meal(m) for m in meals]


@router.get("/meals/availability", response_model=list[MealAvailability])
def check_all_meals(db: DbSession) -> list[MealAvailability]:
    """Check which meals can be made with current inventory."""
    meals = list(
        db.scalars(select(Meal).options(selectinload(Meal.ingredients).selectinload(MealIngredient.item))).all()
    )
    return [_check_meal(m) for m in meals]


@router.get("/meals/{meal_id}", response_model=MealResponse)
def get_meal(meal_id: int, db: DbSession) -> MealResponse:
    """Get a meal by ID with ingredients."""
    meal = db.scalar(
        select(Meal).where(Meal.id == meal_id).options(selectinload(Meal.ingredients).selectinload(MealIngredient.item))
    )
    if not meal:
        raise HTTPException(status_code=404, detail="Meal not found")
    return MealResponse.from_meal(meal)


@router.delete("/meals/{meal_id}")
def delete_meal(meal_id: int, db: DbSession) -> dict[str, bool]:
    """Delete a meal by ID."""
    meal = db.get(Meal, meal_id)
    if not meal:
        raise HTTPException(status_code=404, detail="Meal not found")
    db.delete(meal)
    db.commit()
    return {"deleted": True}


@router.post("/meals/{meal_id}/ingredients", response_model=MealResponse)
def add_ingredient(meal_id: int, ingredient: IngredientCreate, db: DbSession) -> MealResponse:
    """Add an ingredient to a meal."""
    meal = db.get(Meal, meal_id)
    if not meal:
        raise HTTPException(status_code=404, detail="Meal not found")
    if not db.get(Item, ingredient.item_id):
        raise HTTPException(status_code=422, detail="Item not found")
    existing = db.scalar(
        select(MealIngredient).where(MealIngredient.meal_id == meal_id, MealIngredient.item_id == ingredient.item_id)
    )
    if existing:
        raise HTTPException(status_code=409, detail="Ingredient already exists for this meal")
    db.add(MealIngredient(meal_id=meal_id, item_id=ingredient.item_id, quantity_needed=ingredient.quantity_needed))
    db.commit()
    meal = db.scalar(
        select(Meal).where(Meal.id == meal_id).options(selectinload(Meal.ingredients).selectinload(MealIngredient.item))
    )
    return MealResponse.from_meal(meal)


@router.delete("/meals/{meal_id}/ingredients/{item_id}")
def remove_ingredient(meal_id: int, item_id: int, db: DbSession) -> dict[str, bool]:
    """Remove an ingredient from a meal."""
    mi = db.scalar(select(MealIngredient).where(MealIngredient.meal_id == meal_id, MealIngredient.item_id == item_id))
    if not mi:
        raise HTTPException(status_code=404, detail="Ingredient not found")
    db.delete(mi)
    db.commit()
    return {"deleted": True}


@router.get("/meals/{meal_id}/availability", response_model=MealAvailability)
def check_meal(meal_id: int, db: DbSession) -> MealAvailability:
    """Check if a specific meal can be made and what's missing."""
    meal = db.scalar(
        select(Meal).where(Meal.id == meal_id).options(selectinload(Meal.ingredients).selectinload(MealIngredient.item))
    )
    if not meal:
        raise HTTPException(status_code=404, detail="Meal not found")
    return _check_meal(meal)


def _check_meal(meal: Meal) -> MealAvailability:
    missing = [
        ShoppingItem(
            item_name=mi.item.name,
            unit=mi.item.unit,
            needed=mi.quantity_needed,
            have=mi.item.quantity,
            short=mi.quantity_needed - mi.item.quantity,
        )
        for mi in meal.ingredients
        if mi.item.quantity < mi.quantity_needed
    ]
    return MealAvailability(
        meal_id=meal.id,
        meal_name=meal.name,
        can_make=len(missing) == 0,
        missing=missing,
    )
