# app/api/endpoints/meal_plans.py
"""Meal plan CRUD, shopping list, and prep session endpoints."""
from __future__ import annotations

import asyncio
import json
from datetime import date

from fastapi import APIRouter, Depends, HTTPException

from app.cloud_session import CloudUser, get_session
from app.db.session import get_store
from app.db.store import Store
from app.models.schemas.meal_plan import (
    VALID_MEAL_TYPES,
    CreatePlanRequest,
    GapItem,
    PlanSummary,
    PrepSessionSummary,
    PrepTaskSummary,
    ShoppingListResponse,
    SlotSummary,
    UpdatePlanRequest,
    UpdatePrepTaskRequest,
    UpsertSlotRequest,
)
from app.services.meal_plan.affiliates import get_retailer_links
from app.services.meal_plan.prep_scheduler import build_prep_tasks
from app.services.meal_plan.shopping_list import compute_shopping_list
from app.tiers import can_use

router = APIRouter()


# ── helpers ───────────────────────────────────────────────────────────────────

def _slot_summary(row: dict) -> SlotSummary:
    return SlotSummary(
        id=row["id"],
        plan_id=row["plan_id"],
        day_of_week=row["day_of_week"],
        meal_type=row["meal_type"],
        recipe_id=row.get("recipe_id"),
        recipe_title=row.get("recipe_title"),
        servings=row["servings"],
        custom_label=row.get("custom_label"),
    )


def _plan_summary(plan: dict, slots: list[dict]) -> PlanSummary:
    meal_types = plan.get("meal_types") or ["dinner"]
    if isinstance(meal_types, str):
        meal_types = json.loads(meal_types)
    return PlanSummary(
        id=plan["id"],
        week_start=plan["week_start"],
        meal_types=meal_types,
        slots=[_slot_summary(s) for s in slots],
        created_at=plan["created_at"],
    )


def _prep_task_summary(row: dict) -> PrepTaskSummary:
    return PrepTaskSummary(
        id=row["id"],
        recipe_id=row.get("recipe_id"),
        task_label=row["task_label"],
        duration_minutes=row.get("duration_minutes"),
        sequence_order=row["sequence_order"],
        equipment=row.get("equipment"),
        is_parallel=bool(row.get("is_parallel", False)),
        notes=row.get("notes"),
        user_edited=bool(row.get("user_edited", False)),
    )


# ── plan CRUD ─────────────────────────────────────────────────────────────────

@router.post("/", response_model=PlanSummary)
async def create_plan(
    req: CreatePlanRequest,
    session: CloudUser = Depends(get_session),
    store: Store = Depends(get_store),
) -> PlanSummary:
    import sqlite3

    # Free tier is locked to dinner-only; paid+ may configure meal types
    if can_use("meal_plan_config", session.tier):
        meal_types = [t for t in req.meal_types if t in VALID_MEAL_TYPES] or ["dinner"]
    else:
        meal_types = ["dinner"]

    try:
        plan = await asyncio.to_thread(store.create_meal_plan, str(req.week_start), meal_types)
    except sqlite3.IntegrityError:
        raise HTTPException(
            status_code=409,
            detail=f"A meal plan for the week of {req.week_start} already exists.",
        )
    slots = await asyncio.to_thread(store.get_plan_slots, plan["id"])
    return _plan_summary(plan, slots)


@router.get("/", response_model=list[PlanSummary])
async def list_plans(
    session: CloudUser = Depends(get_session),
    store: Store = Depends(get_store),
) -> list[PlanSummary]:
    plans = await asyncio.to_thread(store.list_meal_plans)
    result = []
    for p in plans:
        slots = await asyncio.to_thread(store.get_plan_slots, p["id"])
        result.append(_plan_summary(p, slots))
    return result


@router.patch("/{plan_id}", response_model=PlanSummary)
async def update_plan(
    plan_id: int,
    req: UpdatePlanRequest,
    session: CloudUser = Depends(get_session),
    store: Store = Depends(get_store),
) -> PlanSummary:
    plan = await asyncio.to_thread(store.get_meal_plan, plan_id)
    if plan is None:
        raise HTTPException(status_code=404, detail="Plan not found.")
    # Free tier stays dinner-only; paid+ may add meal types
    if can_use("meal_plan_config", session.tier):
        meal_types = [t for t in req.meal_types if t in VALID_MEAL_TYPES] or ["dinner"]
    else:
        meal_types = ["dinner"]
    updated = await asyncio.to_thread(store.update_meal_plan_types, plan_id, meal_types)
    if updated is None:
        raise HTTPException(status_code=404, detail="Plan not found.")
    slots = await asyncio.to_thread(store.get_plan_slots, plan_id)
    return _plan_summary(updated, slots)


@router.get("/{plan_id}", response_model=PlanSummary)
async def get_plan(
    plan_id: int,
    session: CloudUser = Depends(get_session),
    store: Store = Depends(get_store),
) -> PlanSummary:
    plan = await asyncio.to_thread(store.get_meal_plan, plan_id)
    if plan is None:
        raise HTTPException(status_code=404, detail="Plan not found.")
    slots = await asyncio.to_thread(store.get_plan_slots, plan_id)
    return _plan_summary(plan, slots)


# ── slots ─────────────────────────────────────────────────────────────────────

@router.put("/{plan_id}/slots/{day_of_week}/{meal_type}", response_model=SlotSummary)
async def upsert_slot(
    plan_id: int,
    day_of_week: int,
    meal_type: str,
    req: UpsertSlotRequest,
    session: CloudUser = Depends(get_session),
    store: Store = Depends(get_store),
) -> SlotSummary:
    if day_of_week < 0 or day_of_week > 6:
        raise HTTPException(status_code=422, detail="day_of_week must be 0-6.")
    if meal_type not in VALID_MEAL_TYPES:
        raise HTTPException(status_code=422, detail=f"Invalid meal_type '{meal_type}'.")
    plan = await asyncio.to_thread(store.get_meal_plan, plan_id)
    if plan is None:
        raise HTTPException(status_code=404, detail="Plan not found.")
    row = await asyncio.to_thread(
        store.upsert_slot,
        plan_id, day_of_week, meal_type,
        req.recipe_id, req.servings, req.custom_label,
    )
    return _slot_summary(row)


@router.delete("/{plan_id}/slots/{slot_id}", status_code=204)
async def delete_slot(
    plan_id: int,
    slot_id: int,
    session: CloudUser = Depends(get_session),
    store: Store = Depends(get_store),
) -> None:
    plan = await asyncio.to_thread(store.get_meal_plan, plan_id)
    if plan is None:
        raise HTTPException(status_code=404, detail="Plan not found.")
    await asyncio.to_thread(store.delete_slot, slot_id)


# ── shopping list ─────────────────────────────────────────────────────────────

@router.get("/{plan_id}/shopping-list", response_model=ShoppingListResponse)
async def get_shopping_list(
    plan_id: int,
    session: CloudUser = Depends(get_session),
    store: Store = Depends(get_store),
) -> ShoppingListResponse:
    plan = await asyncio.to_thread(store.get_meal_plan, plan_id)
    if plan is None:
        raise HTTPException(status_code=404, detail="Plan not found.")

    recipes = await asyncio.to_thread(store.get_plan_recipes, plan_id)
    inventory = await asyncio.to_thread(store.list_inventory)

    gaps, covered = compute_shopping_list(recipes, inventory)

    # Enrich gap items with retailer links
    def _to_schema(item, enrich: bool) -> GapItem:
        links = get_retailer_links(item.ingredient_name) if enrich else []
        return GapItem(
            ingredient_name=item.ingredient_name,
            needed_raw=item.needed_raw,
            have_quantity=item.have_quantity,
            have_unit=item.have_unit,
            covered=item.covered,
            retailer_links=links,
        )

    gap_items = [_to_schema(g, enrich=True) for g in gaps]
    covered_items = [_to_schema(c, enrich=False) for c in covered]

    disclosure = (
        "Some links may be affiliate links. Purchases through them support Kiwi development."
        if gap_items else None
    )

    return ShoppingListResponse(
        plan_id=plan_id,
        gap_items=gap_items,
        covered_items=covered_items,
        disclosure=disclosure,
    )


# ── prep session ──────────────────────────────────────────────────────────────

@router.get("/{plan_id}/prep-session", response_model=PrepSessionSummary)
async def get_prep_session(
    plan_id: int,
    session: CloudUser = Depends(get_session),
    store: Store = Depends(get_store),
) -> PrepSessionSummary:
    plan = await asyncio.to_thread(store.get_meal_plan, plan_id)
    if plan is None:
        raise HTTPException(status_code=404, detail="Plan not found.")
    prep_session = await asyncio.to_thread(store.get_prep_session_for_plan, plan_id)
    if prep_session is None:
        raise HTTPException(status_code=404, detail="No prep session for this plan.")
    raw_tasks = await asyncio.to_thread(store.get_prep_tasks, prep_session["id"])
    return PrepSessionSummary(
        id=prep_session["id"],
        plan_id=plan_id,
        scheduled_date=prep_session["scheduled_date"],
        status=prep_session["status"],
        tasks=[_prep_task_summary(t) for t in raw_tasks],
    )


@router.post("/{plan_id}/prep-session", response_model=PrepSessionSummary)
async def create_prep_session(
    plan_id: int,
    session: CloudUser = Depends(get_session),
    store: Store = Depends(get_store),
) -> PrepSessionSummary:
    plan = await asyncio.to_thread(store.get_meal_plan, plan_id)
    if plan is None:
        raise HTTPException(status_code=404, detail="Plan not found.")

    slots = await asyncio.to_thread(store.get_plan_slots, plan_id)
    recipes = await asyncio.to_thread(store.get_plan_recipes, plan_id)
    prep_tasks = build_prep_tasks(slots=slots, recipes=recipes)

    scheduled_date = date.today().isoformat()
    prep_session = await asyncio.to_thread(
        store.create_prep_session, plan_id, scheduled_date
    )
    session_id = prep_session["id"]

    task_dicts = [
        {
            "recipe_id": t.recipe_id,
            "slot_id": t.slot_id,
            "task_label": t.task_label,
            "duration_minutes": t.duration_minutes,
            "sequence_order": t.sequence_order,
            "equipment": t.equipment,
            "is_parallel": t.is_parallel,
            "notes": t.notes,
        }
        for t in prep_tasks
    ]
    inserted = await asyncio.to_thread(store.bulk_insert_prep_tasks, session_id, task_dicts)

    return PrepSessionSummary(
        id=prep_session["id"],
        plan_id=prep_session["plan_id"],
        scheduled_date=prep_session["scheduled_date"],
        status=prep_session["status"],
        tasks=[_prep_task_summary(r) for r in inserted],
    )


@router.patch(
    "/{plan_id}/prep-session/tasks/{task_id}",
    response_model=PrepTaskSummary,
)
async def update_prep_task(
    plan_id: int,
    task_id: int,
    req: UpdatePrepTaskRequest,
    session: CloudUser = Depends(get_session),
    store: Store = Depends(get_store),
) -> PrepTaskSummary:
    updated = await asyncio.to_thread(
        store.update_prep_task,
        task_id,
        duration_minutes=req.duration_minutes,
        sequence_order=req.sequence_order,
        notes=req.notes,
        equipment=req.equipment,
    )
    if updated is None:
        raise HTTPException(status_code=404, detail="Task not found.")
    return _prep_task_summary(updated)
