from __future__ import annotations

from typing import List
from uuid import UUID

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

from app.database import get_db
from app.models import Ingredient, NeverSuggest, NeverSuggestReason, Recipe
from app.schemas.never_suggest import NeverSuggestCreate, NeverSuggestRead
from app.security import require_admin, require_session


public_router = APIRouter(prefix="/api/never-suggest", tags=["never-suggest"])
admin_router = APIRouter(
    prefix="/api/admin/never-suggest",
    tags=["never-suggest-admin"],
    dependencies=[Depends(require_admin)],
)


def _coerce_reason(raw: str | None) -> NeverSuggestReason | None:
    if raw is None:
        return None
    try:
        return NeverSuggestReason(raw)
    except ValueError:
        raise HTTPException(status_code=422, detail=f"unknown reason: {raw}")


def _attach_names(rows: List[NeverSuggest], db: Session) -> List[dict]:
    """Hydrate recipe_name / ingredient_name for the response.

    One LEFT OUTER JOIN per kind, then merge into the response dicts.
    A second pass would be a `selectinload` if the list grows; for the
    family-scale (dozens of rows) this is simpler and fast enough.
    """
    recipe_ids = {r.recipe_id for r in rows if r.recipe_id}
    ingredient_ids = {r.ingredient_id for r in rows if r.ingredient_id}
    recipe_map: dict[UUID, str] = {}
    if recipe_ids:
        for rid, name in db.query(Recipe.id, Recipe.name).filter(Recipe.id.in_(recipe_ids)).all():
            recipe_map[rid] = name
    ingredient_map: dict[UUID, str] = {}
    if ingredient_ids:
        for iid, name in db.query(Ingredient.id, Ingredient.name).filter(Ingredient.id.in_(ingredient_ids)).all():
            ingredient_map[iid] = name
    out = []
    for r in rows:
        d = {
            "id": r.id,
            "family_profile_id": r.family_profile_id,
            "ingredient_id": r.ingredient_id,
            "recipe_id": r.recipe_id,
            "reason": r.reason.value if r.reason else None,
            "notes": r.notes,
            "recipe_name": recipe_map.get(r.recipe_id) if r.recipe_id else None,
            "ingredient_name": ingredient_map.get(r.ingredient_id) if r.ingredient_id else None,
        }
        out.append(d)
    return out


@public_router.get("", response_model=List[NeverSuggestRead])
def list_for_family(
    family_profile_id: UUID = Query(...),
    db: Session = Depends(get_db),
):
    rows = (
        db.query(NeverSuggest)
        .filter(NeverSuggest.family_profile_id == family_profile_id)
        .all()
    )
    return _attach_names(rows, db)


@public_router.post("", response_model=NeverSuggestRead, status_code=status.HTTP_201_CREATED)
def add_block(
    payload: NeverSuggestCreate,
    db: Session = Depends(get_db),
    _session: str = Depends(require_session),
):
    """Family-facing: mark a recipe (or ingredient) as never-suggest.

    Idempotent on (family_profile_id, recipe_id, reason). Re-adding the
    same row returns the existing row instead of creating a duplicate.
    The on-conflict check is a single SELECT + INSERT; small enough
    that we don't need a unique index.
    """
    existing = (
        db.query(NeverSuggest)
        .filter(
            NeverSuggest.family_profile_id == payload.family_profile_id,
            NeverSuggest.recipe_id == payload.recipe_id,
            NeverSuggest.ingredient_id == payload.ingredient_id,
            NeverSuggest.reason == _coerce_reason(payload.reason),
        )
        .first()
    )
    if existing is not None:
        return _attach_names([existing], db)[0]

    row = NeverSuggest(
        family_profile_id=payload.family_profile_id,
        ingredient_id=payload.ingredient_id,
        recipe_id=payload.recipe_id,
        reason=_coerce_reason(payload.reason),
        notes=payload.notes,
    )
    db.add(row)
    db.commit()
    db.refresh(row)
    return _attach_names([row], db)[0]


@public_router.delete(
    "/{ns_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    response_class=Response,
)
def remove_block(
    ns_id: UUID,
    db: Session = Depends(get_db),
    session: str = Depends(require_session),
) -> Response:
    """Family-facing: undo a never-suggest (used by the toast Undo button).

    require_session resolves to the auto-detected family id. The row's
    family_profile_id must match — otherwise a 403 prevents one family
    from removing another family's block.
    """
    row = db.query(NeverSuggest).filter(NeverSuggest.id == ns_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="never-suggest entry not found")
    if str(row.family_profile_id) != session:
        raise HTTPException(
            status_code=403,
            detail="never-suggest entry belongs to a different family",
        )
    db.delete(row)
    db.commit()
    return Response(status_code=204)


@admin_router.post("", response_model=NeverSuggestRead, status_code=status.HTTP_201_CREATED)
def block(payload: NeverSuggestCreate, db: Session = Depends(get_db)):
    row = NeverSuggest(
        family_profile_id=payload.family_profile_id,
        ingredient_id=payload.ingredient_id,
        recipe_id=payload.recipe_id,
        reason=_coerce_reason(payload.reason),
        notes=payload.notes,
    )
    db.add(row)
    db.commit()
    db.refresh(row)
    return _attach_names([row], db)[0]


@admin_router.delete(
    "/{ns_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    response_class=Response,
)
def unblock(ns_id: UUID, db: Session = Depends(get_db)) -> Response:
    row = db.query(NeverSuggest).filter(NeverSuggest.id == ns_id).first()
    if row is None:
        raise HTTPException(status_code=404, detail="never-suggest entry not found")
    db.delete(row)
    db.commit()
    return Response(status_code=204)
