import type { FastifyInstance } from "fastify";
import { and, desc, eq } from "drizzle-orm";
import { schema } from "@app/database";
import {
  consumeMealBoxInputSchema,
  createMealBoxInputSchema,
  dayQuerySchema,
  idParamSchema,
  logMealInputSchema,
} from "@app/validation";
import {
  computeItemNutrition,
  DEFAULT_TARGETS,
  computeDailyTargets,
  scaleNutrition,
  summarizeDay,
  sumNutrition,
} from "@app/nutrition-engine";
import { EMPTY_NUTRITION, type NutritionValues } from "@app/shared-types";
import { errors, parse } from "../lib/errors.js";
import { emitEvent, requireActiveHousehold, requireMembership, todayIso } from "../lib/helpers.js";

/**
 * Måltidsloggning + "Min dag" (spec §4.3, §23) och matlådor (spec §24).
 * Näringsvärden härleds deterministiskt – aldrig av AI (spec §61.1).
 */
export async function mealRoutes(app: FastifyInstance) {
  const auth = { preHandler: [app.authenticate] };

  app.post("/v1/meals", auth, async (req, reply) => {
    const input = parse(logMealInputSchema, req.body);
    const householdId = await requireActiveHousehold(app.db, req.userId).catch(() => null);

    let nutrition: NutritionValues;
    let isEstimate = false;
    let estimateMin: number | null = null;
    let estimateMax: number | null = null;

    if (input.nutritionOverride) {
      // Användarens egen inmatning eller bekräftat foto-intervall.
      nutrition = { ...EMPTY_NUTRITION, ...input.nutritionOverride };
      isEstimate = input.source === "plate_photo";
    } else if (input.recipeId) {
      const [recipe] = await app.db
        .select()
        .from(schema.recipes)
        .where(eq(schema.recipes.id, input.recipeId))
        .limit(1);
      if (!recipe) throw errors.notFound("Receptet finns inte.");
      nutrition = scaleNutrition(recipe.nutritionPerPortion, input.portionFraction);
    } else if (input.items.length > 0) {
      const parts: NutritionValues[] = [];
      for (const item of input.items) {
        if (item.nutrition) {
          parts.push({ ...EMPTY_NUTRITION, ...item.nutrition });
          continue;
        }
        if (item.canonicalIngredientId && item.quantity != null && item.unit) {
          const [ing] = await app.db
            .select()
            .from(schema.canonicalIngredients)
            .where(eq(schema.canonicalIngredients.id, item.canonicalIngredientId))
            .limit(1);
          if (ing) {
            const computed = computeItemNutrition(item.quantity, item.unit, ing.nutritionPer100, {
              densityGPerMl: ing.densityGPerMl,
              gramsPerPiece: ing.gramsPerPiece,
            });
            if (computed) {
              parts.push(computed);
              continue;
            }
          }
        }
        throw errors.badRequest(
          `"${item.displayName}" saknar näringsdata. Ange mängd + känd ingrediens, eller egna värden.`,
        );
      }
      nutrition = sumNutrition(parts);
    } else if (input.scanJobId) {
      // Tallriksfoto: intervall från AAMOS som användaren bekräftar (spec §22).
      const [job] = await app.db
        .select()
        .from(schema.scanJobs)
        .where(and(eq(schema.scanJobs.id, input.scanJobId), eq(schema.scanJobs.userId, req.userId)))
        .limit(1);
      if (!job?.result) throw errors.badRequest("Skanningen har inget resultat.");
      const result = job.result as { kcalRange?: { min: number; max: number; mostLikely: number } };
      if (!result.kcalRange) throw errors.badRequest("Skanningen saknar kaloriuppskattning.");
      nutrition = { ...EMPTY_NUTRITION, kcal: result.kcalRange.mostLikely };
      isEstimate = true;
      estimateMin = result.kcalRange.min;
      estimateMax = result.kcalRange.max;
    } else {
      throw errors.badRequest("Ange recept, livsmedel, skanning eller egna näringsvärden.");
    }

    const [meal] = await app.db
      .insert(schema.meals)
      .values({
        userId: req.userId,
        householdId,
        date: input.date,
        mealType: input.mealType,
        source: input.source,
        recipeId: input.recipeId ?? null,
        titleSv: input.titleSv,
        portionFraction: input.portionFraction,
        nutrition,
        nutritionIsEstimate: isEstimate,
        estimateMinKcal: estimateMin,
        estimateMaxKcal: estimateMax,
        items: input.items.length > 0 ? input.items : null,
        scanJobId: input.scanJobId ?? null,
      })
      .returning();

    await emitEvent(app.db, {
      type: "MEAL_LOGGED",
      payload: {
        mealId: meal!.id,
        mealType: input.mealType,
        kcal: nutrition.kcal,
        source: input.source,
      },
      userId: req.userId,
      householdId: householdId ?? undefined,
      correlationId: req.correlationId,
    });

    return reply.status(201).send(meal);
  });

  /** "Min dag" (spec §4.3): måltider + summering mot personliga mål. */
  app.get("/v1/meals/day", auth, async (req) => {
    const { date } = parse(dayQuerySchema, req.query);
    const meals = await app.db
      .select()
      .from(schema.meals)
      .where(and(eq(schema.meals.userId, req.userId), eq(schema.meals.date, date)))
      .orderBy(schema.meals.loggedAt);

    const [profile] = await app.db
      .select()
      .from(schema.userHealthProfiles)
      .where(eq(schema.userHealthProfiles.userId, req.userId))
      .limit(1);
    const [prefs] = await app.db
      .select()
      .from(schema.userPreferences)
      .where(eq(schema.userPreferences.userId, req.userId))
      .limit(1);

    const targets =
      profile?.weightKg && profile.heightCm && profile.birthYear
        ? computeDailyTargets({
            sex: profile.sex ?? "unspecified",
            age: new Date().getUTCFullYear() - profile.birthYear,
            heightCm: profile.heightCm,
            weightKg: profile.weightKg,
            activityLevel: profile.activityLevel,
            primaryGoal: prefs?.primaryGoal ?? undefined,
          }).targets
        : DEFAULT_TARGETS;

    const summary = summarizeDay(
      meals.map((m) => m.nutrition),
      targets,
    );

    const hasEstimates = meals.some((m) => m.nutritionIsEstimate);
    return {
      date,
      meals,
      summary,
      note: hasEstimates
        ? "Dagen innehåller uppskattade värden från foto – justera gärna vid behov."
        : "Värdena är beräknade ur recept och livsmedelsdata och visas som uppskattningar.",
    };
  });

  app.delete("/v1/meals/:id", auth, async (req) => {
    const { id } = parse(idParamSchema, req.params);
    const [meal] = await app.db
      .select()
      .from(schema.meals)
      .where(and(eq(schema.meals.id, id), eq(schema.meals.userId, req.userId)))
      .limit(1);
    if (!meal) throw errors.notFound();
    await app.db.delete(schema.meals).where(eq(schema.meals.id, id));
    return { ok: true };
  });

  // ---------------------------------------------------------------------
  // Matlådor (spec §24)
  // ---------------------------------------------------------------------

  app.get("/v1/meal-boxes", auth, async (req) => {
    const householdId = await requireActiveHousehold(app.db, req.userId);
    const boxes = await app.db
      .select()
      .from(schema.mealBoxes)
      .where(
        and(
          eq(schema.mealBoxes.householdId, householdId),
          eq(schema.mealBoxes.status, "available"),
        ),
      )
      .orderBy(schema.mealBoxes.recommendedUseBy);
    return { mealBoxes: boxes };
  });

  app.post("/v1/meal-boxes", auth, async (req, reply) => {
    const input = parse(createMealBoxInputSchema, req.body);
    const householdId = await requireActiveHousehold(app.db, req.userId);

    let nutritionPerPortion = null;
    if (input.recipeId) {
      const [recipe] = await app.db
        .select({ nutrition: schema.recipes.nutritionPerPortion })
        .from(schema.recipes)
        .where(eq(schema.recipes.id, input.recipeId))
        .limit(1);
      nutritionPerPortion = recipe?.nutrition ?? null;
    }

    const cookedAt = input.cookedAt ?? todayIso();
    const useByDays = input.frozen ? 90 : 3;
    const [box] = await app.db
      .insert(schema.mealBoxes)
      .values({
        householdId,
        recipeId: input.recipeId ?? null,
        titleSv: input.titleSv,
        portions: input.portions,
        portionsRemaining: input.portions,
        nutritionPerPortion,
        cookedAt,
        storageLocationId: input.storageLocationId,
        frozen: input.frozen,
        recommendedUseBy: new Date(Date.parse(cookedAt) + useByDays * 86_400_000)
          .toISOString()
          .slice(0, 10),
        reservedForUserId: input.reservedForUserId ?? null,
      })
      .returning();

    await emitEvent(app.db, {
      type: "MEAL_BOX_CREATED",
      payload: { mealBoxId: box!.id, portions: input.portions },
      userId: req.userId,
      householdId,
      correlationId: req.correlationId,
    });
    return reply.status(201).send(box);
  });

  app.post("/v1/meal-boxes/:id/consume", auth, async (req) => {
    const { id } = parse(idParamSchema, req.params);
    const input = parse(consumeMealBoxInputSchema, req.body);
    const [box] = await app.db
      .select()
      .from(schema.mealBoxes)
      .where(eq(schema.mealBoxes.id, id))
      .limit(1);
    if (!box) throw errors.notFound("Matlådan finns inte.");
    await requireMembership(app.db, box.householdId, req.userId);
    if (box.portionsRemaining < input.portions) {
      throw errors.conflict(`Bara ${box.portionsRemaining} portioner kvar.`);
    }

    const remaining = box.portionsRemaining - input.portions;
    await app.db
      .update(schema.mealBoxes)
      .set({ portionsRemaining: remaining, status: remaining <= 0 ? "consumed" : box.status })
      .where(eq(schema.mealBoxes.id, id));

    let mealId: string | null = null;
    if (input.logAsMeal && box.nutritionPerPortion) {
      const [meal] = await app.db
        .insert(schema.meals)
        .values({
          userId: req.userId,
          householdId: box.householdId,
          date: input.date ?? todayIso(),
          mealType: input.mealType,
          source: "meal_box",
          recipeId: box.recipeId,
          titleSv: box.titleSv,
          portionFraction: input.portions,
          nutrition: scaleNutrition(box.nutritionPerPortion, input.portions),
          nutritionIsEstimate: false,
        })
        .returning();
      mealId = meal!.id;
    }

    await emitEvent(app.db, {
      type: "MEAL_BOX_CONSUMED",
      payload: { mealBoxId: id, portions: input.portions },
      userId: req.userId,
      householdId: box.householdId,
      correlationId: req.correlationId,
    });
    return { ok: true, portionsRemaining: remaining, mealId };
  });

  app.post("/v1/meal-boxes/:id/discard", auth, async (req) => {
    const { id } = parse(idParamSchema, req.params);
    const [box] = await app.db
      .select()
      .from(schema.mealBoxes)
      .where(eq(schema.mealBoxes.id, id))
      .limit(1);
    if (!box) throw errors.notFound();
    await requireMembership(app.db, box.householdId, req.userId);
    await app.db
      .update(schema.mealBoxes)
      .set({ status: "discarded", portionsRemaining: 0 })
      .where(eq(schema.mealBoxes.id, id));
    return { ok: true };
  });

  /** Måltidshistorik ("tidigare måltid" som loggkälla, spec §23). */
  app.get("/v1/meals/recent", auth, async (req) => {
    const meals = await app.db
      .select()
      .from(schema.meals)
      .where(eq(schema.meals.userId, req.userId))
      .orderBy(desc(schema.meals.loggedAt))
      .limit(20);
    return { meals };
  });
}
