import uuid

from fastapi import APIRouter, Depends, HTTPException, UploadFile
from slugify import slugify
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.database import UPLOAD_DIR, get_db
from app.models import Ingredient, Recipe, Step
from app.schemas import RecipeIn, RecipeOut, RecipeSummary

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

ALLOWED_IMAGE_TYPES = {"image/jpeg": ".jpg", "image/png": ".png", "image/webp": ".webp"}


def _unique_slug(db: Session, name: str, *, exclude_id: int | None = None) -> str:
    base = slugify(name) or "recipe"
    slug = base
    suffix = 2
    while True:
        stmt = select(Recipe.id).where(Recipe.slug == slug)
        if exclude_id is not None:
            stmt = stmt.where(Recipe.id != exclude_id)
        if db.scalar(stmt) is None:
            return slug
        slug = f"{base}-{suffix}"
        suffix += 1


def _apply_children(db: Session, recipe: Recipe, payload: RecipeIn) -> None:
    recipe.ingredients.clear()
    for position, ing in enumerate(payload.ingredients):
        recipe.ingredients.append(
            Ingredient(
                position=position,
                kind=ing.kind,
                quantity=ing.quantity,
                unit=ing.unit,
                text=ing.text,
            )
        )
    recipe.steps.clear()
    for position, step in enumerate(payload.steps):
        recipe.steps.append(Step(position=position, text=step.text))


@router.get("", response_model=list[RecipeSummary])
def list_recipes(search: str | None = None, db: Session = Depends(get_db)):
    stmt = select(Recipe).order_by(Recipe.name)
    if search:
        like = f"%{search.lower()}%"
        stmt = stmt.where(
            Recipe.name.ilike(like) | Recipe.category.ilike(like)
        )
    return db.scalars(stmt).all()


@router.get("/{recipe_id}", response_model=RecipeOut)
def get_recipe(recipe_id: int, db: Session = Depends(get_db)):
    recipe = db.get(Recipe, recipe_id)
    if recipe is None:
        raise HTTPException(status_code=404, detail="Recipe not found")
    return recipe


@router.post("", response_model=RecipeOut, status_code=201)
def create_recipe(payload: RecipeIn, db: Session = Depends(get_db)):
    recipe = Recipe(
        name=payload.name,
        slug=_unique_slug(db, payload.name),
        category=payload.category,
        preptime=payload.preptime,
        cooktime=payload.cooktime,
        baketime=payload.baketime,
        cooltime=payload.cooltime,
        marinade_time=payload.marinade_time,
        robot_time=payload.robot_time,
        servings=payload.servings,
        notes=payload.notes,
        kcal=payload.kcal,
        kh=payload.kh,
        protein=payload.protein,
        fett=payload.fett,
    )
    _apply_children(db, recipe, payload)
    db.add(recipe)
    db.commit()
    db.refresh(recipe)
    return recipe


@router.put("/{recipe_id}", response_model=RecipeOut)
def update_recipe(recipe_id: int, payload: RecipeIn, db: Session = Depends(get_db)):
    recipe = db.get(Recipe, recipe_id)
    if recipe is None:
        raise HTTPException(status_code=404, detail="Recipe not found")

    if payload.name != recipe.name:
        recipe.slug = _unique_slug(db, payload.name, exclude_id=recipe_id)
    recipe.name = payload.name
    recipe.category = payload.category
    recipe.preptime = payload.preptime
    recipe.cooktime = payload.cooktime
    recipe.baketime = payload.baketime
    recipe.cooltime = payload.cooltime
    recipe.marinade_time = payload.marinade_time
    recipe.robot_time = payload.robot_time
    recipe.servings = payload.servings
    recipe.notes = payload.notes
    recipe.kcal = payload.kcal
    recipe.kh = payload.kh
    recipe.protein = payload.protein
    recipe.fett = payload.fett
    _apply_children(db, recipe, payload)

    db.commit()
    db.refresh(recipe)
    return recipe


@router.delete("/{recipe_id}", status_code=204)
def delete_recipe(recipe_id: int, db: Session = Depends(get_db)):
    recipe = db.get(Recipe, recipe_id)
    if recipe is None:
        raise HTTPException(status_code=404, detail="Recipe not found")
    if recipe.image_filename:
        (UPLOAD_DIR / recipe.image_filename).unlink(missing_ok=True)
    db.delete(recipe)
    db.commit()


@router.post("/{recipe_id}/image", response_model=RecipeOut)
async def upload_recipe_image(recipe_id: int, file: UploadFile, db: Session = Depends(get_db)):
    recipe = db.get(Recipe, recipe_id)
    if recipe is None:
        raise HTTPException(status_code=404, detail="Recipe not found")
    ext = ALLOWED_IMAGE_TYPES.get(file.content_type or "")
    if ext is None:
        raise HTTPException(status_code=400, detail="Only JPEG, PNG or WEBP images are allowed")

    if recipe.image_filename:
        (UPLOAD_DIR / recipe.image_filename).unlink(missing_ok=True)

    filename = f"{uuid.uuid4().hex}{ext}"
    dest = UPLOAD_DIR / filename
    dest.write_bytes(await file.read())

    recipe.image_filename = filename
    db.commit()
    db.refresh(recipe)
    return recipe


@router.delete("/{recipe_id}/image", response_model=RecipeOut)
def delete_recipe_image(recipe_id: int, db: Session = Depends(get_db)):
    recipe = db.get(Recipe, recipe_id)
    if recipe is None:
        raise HTTPException(status_code=404, detail="Recipe not found")
    if recipe.image_filename:
        (UPLOAD_DIR / recipe.image_filename).unlink(missing_ok=True)
        recipe.image_filename = None
        db.commit()
        db.refresh(recipe)
    return recipe
