import type { FastifyInstance } from "fastify";
import { and, eq, gt, isNull, sql } from "drizzle-orm";
import { schema, markMilestone } from "@app/database";
import { allocateFefo, updateCookingAssumptionProfile } from "@app/inventory-engine";
import { scaleNutrition } from "@app/nutrition-engine";
import type { Unit } from "@app/shared-types";
import { cookingSessionStarted, cookingSessionCompleted } from "@app/analytics";
import { todayIso, emitEvent, trackProductAnalytics } from "./helpers.js";
import { errors } from "./errors.js";
import { loadFullRecipe } from "../routes/recipes.js";
import { userLanguageTag } from "./contentLanguage.js";
import { t } from "./i18n.js";
import { MEAL_TYPES } from "@app/shared-types";

export interface CompleteCookingInput {
  portionsCooked?: number;
  mealBoxPortions?: number;
  mealBoxFrozen?: boolean;
  mealBoxStorageLocationId?: string;
  deductInventory?: boolean;
  eaters?: Array<{ userId: string; portionFraction: number }>;
  date?: string;
  mealType?: string;
  inventoryOverrides?: Array<{ canonicalIngredientId: string; quantityUsed: number; unit: string }>;
  actualPortionsEaten?: number;
  leftoverEstimatePortions?: number;
  leftoverNote?: string;
}

export interface CompleteCookingSessionOptions {
  /** Sant för legacy POST /v1/recipes/:id/cook där session skapas och startas i ett steg. */
  emitStartedEvent?: boolean;
}

export interface CompleteCookingResult {
  ok: boolean;
  mealIds: string[];
  mealBoxId: string | null;
  inventoryDeductions: Array<{ itemId: string; quantity: number; unit: string; name: string }>;
  recipeIngredients: Array<{ canonicalIngredientId: string; displayName: string; quantity: number; unit: string; optional: boolean }>;
}

/**
 * Delad kompletteringsväg för både POST /v1/recipes/:id/cook (legacy)
 * och POST /v1/cooking-sessions/:id/complete. Hanterar summavalidering,
 * persistens av svar, profiluppdateringar och analytics.
 */
export async function completeCookingSession(
  app: FastifyInstance,
  session: typeof schema.cookingSessions.$inferSelect,
  userId: string,
  input: CompleteCookingInput,
  correlationId: string,
  options: CompleteCookingSessionOptions = {},
): Promise<CompleteCookingResult & { session: typeof schema.cookingSessions.$inferSelect }> {
  const plannedPortions = input.portionsCooked ?? session.plannedPortions;
  const mealBoxPortions = input.mealBoxPortions ?? 0;
  const actualPortionsEaten = input.actualPortionsEaten ?? Math.max(0, plannedPortions - mealBoxPortions);
  const leftoverEstimatePortions = input.leftoverEstimatePortions ?? mealBoxPortions;

  if (actualPortionsEaten + leftoverEstimatePortions > plannedPortions) {
    const languageTag = await userLanguageTag(app.db, userId);
    throw errors.badRequest(t("cooked.portionsSumExceedsPlanned", languageTag));
  }

  if (options.emitStartedEvent && session.status === "started") {
    await trackProductAnalytics(
      app.db,
      userId,
      cookingSessionStarted({
        householdId: session.householdId,
        properties: {
          cookingSessionId: session.id,
          recipeId: session.recipeId,
          status: session.status,
          plannedPortions,
        },
      }),
    );
  }

  const result = await completeCookingSessionCore(
    app,
    session,
    userId,
    { ...input, portionsCooked: plannedPortions, mealBoxPortions },
    correlationId,
  );

  // Persistera användarens svar (ersätter kärnans default-värden).
  const [updated] = await app.db
    .update(schema.cookingSessions)
    .set({
      actualPortionsEaten,
      leftoverEstimatePortions,
      leftoverNote: input.leftoverNote ?? null,
      updatedAt: new Date(),
    })
    .where(eq(schema.cookingSessions.id, session.id))
    .returning();
  if (!updated) throw errors.internal("Kunde inte uppdatera cooking session.");

  // Uppdatera antagandeprofiler per icke-valfri ingrediens (hushållsnivå).
  const date = new Date().toISOString().slice(0, 10);
  for (const ing of result.recipeIngredients) {
    if (ing.optional) continue;
    const [existing] = await app.db
      .select()
      .from(schema.cookingAssumptionProfiles)
      .where(
        and(
          eq(schema.cookingAssumptionProfiles.householdId, session.householdId),
          eq(schema.cookingAssumptionProfiles.canonicalIngredientId, ing.canonicalIngredientId),
        ),
      )
      .limit(1);

    const updatedProfile = updateCookingAssumptionProfile(
      {
        householdId: session.householdId,
        canonicalIngredientId: ing.canonicalIngredientId,
        plannedPortions,
        actualPortionsEaten,
        leftoverEstimatePortions,
        sessionId: session.id,
        date,
      },
      existing ?? { averageEatenPortions: null, averageLeftoverPortions: null, observationCount: 0 },
    );

    await app.db
      .insert(schema.cookingAssumptionProfiles)
      .values({
        householdId: session.householdId,
        canonicalIngredientId: ing.canonicalIngredientId,
        ...updatedProfile,
        updatedAt: new Date(),
      })
      .onConflictDoUpdate({
        target: [
          schema.cookingAssumptionProfiles.householdId,
          schema.cookingAssumptionProfiles.canonicalIngredientId,
        ],
        set: {
          averageEatenPortions: updatedProfile.averageEatenPortions,
          averageLeftoverPortions: updatedProfile.averageLeftoverPortions,
          observationCount: updatedProfile.observationCount,
          lastSessionAnswers: updatedProfile.lastSessionAnswers,
          updatedAt: new Date(),
        },
      });
  }

  await trackProductAnalytics(
    app.db,
    userId,
    cookingSessionCompleted({
      householdId: session.householdId,
      properties: {
        cookingSessionId: session.id,
        recipeId: session.recipeId,
        portionsCooked: plannedPortions,
        actualPortionsEaten,
        leftoverEstimatePortions,
        mealBoxPortions,
      },
    }),
  );

  return { ...result, session: updated };
}

/**
 * Lågnivå-kärna för att "jag har lagat". Används via completeCookingSession
 * av både POST /v1/recipes/:id/cook (legacy shortcut) och
 * POST /v1/cooking-sessions/:id/complete.
 */
export async function completeCookingSessionCore(
  app: FastifyInstance,
  session: typeof schema.cookingSessions.$inferSelect,
  userId: string,
  input: CompleteCookingInput,
  correlationId: string,
): Promise<CompleteCookingResult> {
  const recipe = await loadFullRecipe(app, session.recipeId);
  const householdId = session.householdId;
  const portionsCooked = input.portionsCooked ?? session.plannedPortions;
  const date = input.date ?? todayIso();
  const mealType = (input.mealType ?? session.plannedMealType) as (typeof MEAL_TYPES)[number];

  // 1. FEFO-avdrag
  const deductions: Array<{ itemId: string; quantity: number; unit: string; name: string }> = [];
  if (input.deductInventory !== false) {
    const factor = portionsCooked / recipe.portions;
    const overrides = new Map(input.inventoryOverrides?.map((o) => [o.canonicalIngredientId, o]) ?? []);

    for (const ing of recipe.ingredients) {
      if (ing.optional) continue;
      const override = overrides.get(ing.canonicalIngredientId);
      const requiredQty = override ? override.quantityUsed : ing.quantity * factor;
      const requiredUnit = override ? override.unit : ing.unit;
      if (requiredQty <= 0) continue;

      const stock = await app.db
        .select()
        .from(schema.inventoryItems)
        .where(
          and(
            eq(schema.inventoryItems.householdId, householdId),
            eq(schema.inventoryItems.canonicalIngredientId, ing.canonicalIngredientId),
            isNull(schema.inventoryItems.depletedAt),
            gt(schema.inventoryItems.quantity, 0),
          ),
        );
      if (stock.length === 0) continue;

      const [info] = await app.db
        .select()
        .from(schema.canonicalIngredients)
        .where(eq(schema.canonicalIngredients.id, ing.canonicalIngredientId))
        .limit(1);

      const allocation = allocateFefo(
        requiredQty,
        requiredUnit as Unit,
        stock.map((s) => ({
          id: s.id,
          canonicalIngredientId: s.canonicalIngredientId,
          quantity: s.quantity,
          unit: s.unit,
          bestBeforeDate: s.bestBeforeDate,
          useByDate: s.useByDate,
          openedAt: s.openedAt,
          frozenAt: s.frozenAt,
          thawedAt: s.thawedAt,
          purchasedAt: s.purchasedAt,
        })),
        { densityGPerMl: info?.densityGPerMl, gramsPerPiece: info?.gramsPerPiece },
      );

      for (const alloc of allocation.allocations) {
        const item = stock.find((s) => s.id === alloc.itemId)!;
        const newQty = Math.max(0, Math.round((item.quantity - alloc.quantity) * 1000) / 1000);
        await app.db.insert(schema.inventoryTransactions).values({
          householdId,
          inventoryItemId: alloc.itemId,
          cookingSessionId: session.id,
          type: "cook_use",
          quantityDelta: -(item.quantity - newQty),
          unit: item.unit,
          refType: "recipe_cook",
          refId: session.recipeId,
          actorUserId: userId,
        });
        await app.db
          .update(schema.inventoryItems)
          .set({
            quantity: newQty,
            depletedAt: newQty <= 0 ? new Date() : null,
            updatedAt: new Date(),
          })
          .where(eq(schema.inventoryItems.id, alloc.itemId));
        deductions.push({
          itemId: alloc.itemId,
          quantity: alloc.quantity,
          unit: alloc.unit,
          name: item.displayName,
        });
      }
    }
  }

  // 2. Måltider
  const eaters =
    input.eaters && input.eaters.length > 0
      ? input.eaters
      : [{ userId, portionFraction: 1 }];
  const mealIds: string[] = [];
  for (const eater of eaters) {
    const nutrition = scaleNutrition(recipe.nutritionPerPortion, eater.portionFraction);
    const [meal] = await app.db
      .insert(schema.meals)
      .values({
        userId: eater.userId,
        householdId,
        cookingSessionId: session.id,
        date,
        mealType,
        source: "cooked_recipe",
        recipeId: session.recipeId,
        titleSv: recipe.titleSv,
        portionFraction: eater.portionFraction,
        nutrition,
        nutritionIsEstimate: false,
      })
      .returning();
    mealIds.push(meal!.id);
    await emitEvent(app.db, {
      type: "MEAL_LOGGED",
      payload: { mealId: meal!.id, mealType, kcal: nutrition.kcal, source: "cooked_recipe" },
      userId: eater.userId,
      householdId,
      correlationId,
    });
  }

  // 3. Matlådor
  let mealBoxId: string | null = null;
  const mealBoxPortions = input.mealBoxPortions ?? 0;
  if (mealBoxPortions > 0) {
    const locationId =
      input.mealBoxStorageLocationId ??
      (
        await app.db
          .select({ id: schema.storageLocations.id })
          .from(schema.storageLocations)
          .where(
            and(
              eq(schema.storageLocations.householdId, householdId),
              eq(schema.storageLocations.type, input.mealBoxFrozen ? "freezer" : "fridge"),
            ),
          )
          .limit(1)
      )[0]?.id;
    if (!locationId) throw errors.badRequest("Ingen förvaringsplats för matlådor hittades.");

    const useByDays = input.mealBoxFrozen ? 90 : 3;
    const recommendedUseBy = new Date(Date.parse(date) + useByDays * 86_400_000)
      .toISOString()
      .slice(0, 10);
    const [box] = await app.db
      .insert(schema.mealBoxes)
      .values({
        householdId,
        recipeId: session.recipeId,
        cookingSessionId: session.id,
        titleSv: recipe.titleSv,
        portions: mealBoxPortions,
        portionsRemaining: mealBoxPortions,
        nutritionPerPortion: recipe.nutritionPerPortion,
        cookedAt: date,
        storageLocationId: locationId,
        frozen: input.mealBoxFrozen ?? false,
        recommendedUseBy,
      })
      .returning();
    mealBoxId = box!.id;
    await emitEvent(app.db, {
      type: "MEAL_BOX_CREATED",
      payload: { mealBoxId: box!.id, portions: mealBoxPortions },
      userId,
      householdId,
      correlationId,
    });
  }

  // 4. recipe_cooks + statistik
  await app.db.insert(schema.recipeCooks).values({
    recipeId: session.recipeId,
    userId,
    householdId,
    cookingSessionId: session.id,
    portionsCooked,
    cookedAt: new Date(),
  });
  await app.db
    .update(schema.recipes)
    .set({ cookCount: sql`${schema.recipes.cookCount} + 1` })
    .where(eq(schema.recipes.id, session.recipeId));
  await emitEvent(app.db, {
    type: "RECIPE_COOKED",
    payload: { recipeId: session.recipeId, portions: portionsCooked, mealBoxPortions },
    userId,
    householdId,
    correlationId,
  });

  // 5. Uppdatera session (svaren skrivs över av completeCookingSession).
  await app.db
    .update(schema.cookingSessions)
    .set({
      status: "completed",
      completedAt: new Date(),
      plannedDeductions: deductions,
      updatedAt: new Date(),
    })
    .where(eq(schema.cookingSessions.id, session.id));

  await markMilestone(app.db, householdId, "firstCookingSessionCompletedAt");
  if (deductions.length > 0) {
    await markMilestone(app.db, householdId, "inventoryUpdatedAfterCookingAt");
  }

  return {
    ok: true,
    mealIds,
    mealBoxId,
    inventoryDeductions: deductions,
    recipeIngredients: recipe.ingredients.map((ing) => ({
      canonicalIngredientId: ing.canonicalIngredientId,
      displayName: ing.displayNameSv,
      quantity: ing.quantity,
      unit: ing.unit,
      optional: ing.optional,
    })),
  };
}
