import type { FastifyInstance } from "fastify";
import { and, eq, inArray, lt } from "drizzle-orm";
import { schema, markMilestone } from "@app/database";
import {
  cookingSessionStartInputSchema,
  cookingSessionCompleteInputSchema,
  idParamSchema,
} from "@app/validation";
import { errors, parse } from "../lib/errors.js";
import { requireActiveHousehold, requireMembership } from "../lib/helpers.js";
import { cookingSessionStarted, cookingSessionCancelled } from "@app/analytics";
import { trackProductAnalytics } from "../lib/helpers.js";
import { completeCookingSession, undoCookingSession } from "../lib/cooking.js";
import { z } from "zod";

/**
 * Cooking Sessions (Fas 3 §6).
 *
 * - PLANNED reserverar aldrig lager.
 * - STARTED har 24 h på sig att complete/cancel (§20 timeout).
 * - COMPLETED/CANCELLED är terminala.
 * - Gamla POST /v1/recipes/:id/cook finns kvar som kortkommando.
 */
export async function cookingSessionRoutes(app: FastifyInstance) {
  const auth = { preHandler: [app.authenticate] };

  /** Skapa ny session. Status planned om startNow saknas, annars started. */
  app.post("/v1/recipes/:id/cook/start", auth, async (req, reply) => {
    const { id } = parse(idParamSchema, req.params);
    const input = parse(cookingSessionStartInputSchema, req.body);
    const householdId = await requireActiveHousehold(app.db, req.userId);
    await requireMembership(app.db, householdId, req.userId);

    const [recipe] = await app.db
      .select({ id: schema.recipes.id, portions: schema.recipes.portions })
      .from(schema.recipes)
      .where(eq(schema.recipes.id, id))
      .limit(1);
    if (!recipe) throw errors.notFound("Receptet finns inte.");

    const now = new Date();
    const status = input.startNow ? "started" : "planned";

    const [session] = await app.db
      .insert(schema.cookingSessions)
      .values({
        recipeId: id,
        householdId,
        startedByUserId: req.userId,
        status,
        plannedPortions: input.portions ?? recipe.portions,
        plannedMealType: input.mealType ?? "dinner",
        startedAt: input.startNow ? now : null,
      })
      .returning();

    // cooking_session_started skickas endast vid faktisk start, inte vid planned.
    if (status === "started") {
      await trackProductAnalytics(
        app.db,
        req.userId,
        cookingSessionStarted({
          householdId,
          properties: {
            cookingSessionId: session!.id,
            recipeId: id,
            status,
            plannedPortions: input.portions ?? recipe.portions,
          },
        }),
      );
    }

    return reply.status(201).send({ session: session });
  });

  /** Starta en planned session. */
  app.post("/v1/cooking-sessions/:id/start", auth, async (req) => {
    const { id } = parse(idParamSchema, req.params);
    const session = await getOwnedSession(app, id, req.userId);
    if (session.status !== "planned") throw errors.conflict("Sessionen är inte planerad.");

    const [updated] = await app.db
      .update(schema.cookingSessions)
      .set({ status: "started", startedAt: new Date(), updatedAt: new Date() })
      .where(eq(schema.cookingSessions.id, id))
      .returning();

    await trackProductAnalytics(
      app.db,
      req.userId,
      cookingSessionStarted({
        householdId: session.householdId,
        properties: { cookingSessionId: id, recipeId: session.recipeId, status: "started" },
      }),
    );

    return { session: updated };
  });

  /** Avbryt session utan att röra lagret. */
  app.post("/v1/cooking-sessions/:id/cancel", auth, async (req) => {
    const params = z.object({ id: z.uuid() }).parse(req.params);
    const body = z.object({ reason: z.string().max(200).optional() }).parse(req.body ?? {});
    const session = await getOwnedSession(app, params.id, req.userId);
    if (session.status === "completed" || session.status === "cancelled" || session.status === "undone") {
      throw errors.conflict("Sessionen är redan avslutad.");
    }

    const [updated] = await app.db
      .update(schema.cookingSessions)
      .set({
        status: "cancelled",
        cancelledAt: new Date(),
        cancelReason: body.reason ?? null,
        updatedAt: new Date(),
      })
      .where(eq(schema.cookingSessions.id, params.id))
      .returning();

    await trackProductAnalytics(
      app.db,
      req.userId,
      cookingSessionCancelled({
        householdId: session.householdId,
        properties: { cookingSessionId: params.id, recipeId: session.recipeId },
      }),
    );

    return { session: updated };
  });

  /**
   * Complete en session.
   * Delad kompletteringsväg via completeCookingSession hanterar summavalidering,
   * persistens, profiluppdateringar och analytics.
   */
  app.post("/v1/cooking-sessions/:id/complete", auth, async (req) => {
    const { id } = parse(idParamSchema, req.params);
    const input = parse(cookingSessionCompleteInputSchema, req.body);
    const session = await getOwnedSession(app, id, req.userId);
    if (session.status !== "started") {
      throw errors.conflict("Sessionen måste vara startad för att avslutas.");
    }

    const result = await completeCookingSession(app, session, req.userId, input, req.correlationId);

    return result;
  });

  /** Hämta antagandeprofil för ett recept (per hushåll + ingrediens). */
  app.get("/v1/recipes/:id/cooking-assumptions", auth, async (req) => {
    const { id } = parse(idParamSchema, req.params);
    const householdId = await requireActiveHousehold(app.db, req.userId);
    await requireMembership(app.db, householdId, req.userId);

    const ings = await app.db
      .select({
        canonicalIngredientId: schema.recipeIngredients.canonicalIngredientId,
        optional: schema.recipeIngredients.optional,
      })
      .from(schema.recipeIngredients)
      .where(eq(schema.recipeIngredients.recipeId, id));
    if (ings.length === 0) throw errors.notFound("Receptet finns inte.");

    const nonOptionalIds = ings.filter((i) => !i.optional).map((i) => i.canonicalIngredientId);
    if (nonOptionalIds.length === 0) {
      return {
        householdId,
        recipeId: id,
        defaultActualPortionsEaten: null,
        defaultLeftoverEstimatePortions: null,
        observationCount: 0,
      };
    }

    const profiles = await app.db
      .select()
      .from(schema.cookingAssumptionProfiles)
      .where(
        and(
          eq(schema.cookingAssumptionProfiles.householdId, householdId),
          inArray(schema.cookingAssumptionProfiles.canonicalIngredientId, nonOptionalIds),
        ),
      );

    // Deterministiskt val: profilen med flest observationer (stabil över tid).
    const p = profiles.sort((a, b) => b.observationCount - a.observationCount)[0];

    return {
      householdId,
      recipeId: id,
      defaultActualPortionsEaten: p?.averageEatenPortions ?? null,
      defaultLeftoverEstimatePortions: p?.averageLeftoverPortions ?? null,
      observationCount: p?.observationCount ?? 0,
    };
  });

  /**
   * Ångra en completed session inom 24 h.
   * Ledger är append-only: reverseringstransaktioner skrivs, befintliga
   * transaktioner rörs inte.
   */
  app.post("/v1/cooking-sessions/:id/undo", auth, async (req) => {
    const { id } = parse(idParamSchema, req.params);
    const session = await getOwnedSession(app, id, req.userId);
    const result = await undoCookingSession(app, session, req.userId, req.correlationId);
    return result;
  });

  /** Lista hushållets aktiva sessioner. */
  app.get("/v1/cooking-sessions", auth, async (req) => {
    const householdId = await requireActiveHousehold(app.db, req.userId);
    await requireMembership(app.db, householdId, req.userId);
    const sessions = await app.db
      .select()
      .from(schema.cookingSessions)
      .where(
        and(
          eq(schema.cookingSessions.householdId, householdId),
          eq(schema.cookingSessions.status, "started"),
        ),
      )
      .orderBy(schema.cookingSessions.startedAt);
    return { sessions };
  });
}

async function getOwnedSession(app: FastifyInstance, sessionId: string, userId: string) {
  const [session] = await app.db
    .select()
    .from(schema.cookingSessions)
    .where(eq(schema.cookingSessions.id, sessionId))
    .limit(1);
  if (!session) throw errors.notFound("Sessionen finns inte.");
  await requireMembership(app.db, session.householdId, userId);
  return session;
}
