import type { FastifyInstance } from "fastify";
import { and, desc, eq, gte, sql } from "drizzle-orm";
import { schema } from "@app/database";
import {
  generateWeekPlanInputSchema,
  idParamSchema,
  updatePlanEntryInputSchema,
  weekPlanQuerySchema,
} from "@app/validation";
import { errors, parse } from "../lib/errors.js";
import { emitEvent, requireActiveHousehold, requireMembership } from "../lib/helpers.js";
import { requireFeature } from "../lib/entitlements.js";

/**
 * Veckoplanering (spec §25). Planen genereras asynkront av workern
 * (GENERATE_WEEK_PLAN) som väger lager, utgångsdatum, matlådor, budget,
 * variation och mål – med deterministisk kärna och AAMOS som rådgivare.
 */
export async function planningRoutes(app: FastifyInstance) {
  const auth = { preHandler: [app.authenticate] };

  app.get("/v1/week-plans", auth, async (req) => {
    const q = parse(weekPlanQuerySchema, req.query);
    const householdId = await requireActiveHousehold(app.db, req.userId);
    const conditions = [eq(schema.weekPlans.householdId, householdId)];
    if (q.weekStartDate) conditions.push(eq(schema.weekPlans.weekStartDate, q.weekStartDate));

    const plans = await app.db
      .select()
      .from(schema.weekPlans)
      .where(and(...conditions))
      .orderBy(desc(schema.weekPlans.weekStartDate))
      .limit(8);

    const result = [];
    for (const plan of plans) {
      const entries = await app.db
        .select()
        .from(schema.weekPlanEntries)
        .where(eq(schema.weekPlanEntries.weekPlanId, plan.id))
        .orderBy(schema.weekPlanEntries.date, schema.weekPlanEntries.sortOrder);
      result.push({ ...plan, entries });
    }
    return { plans: result };
  });

  app.post("/v1/week-plans/generate", auth, async (req, reply) => {
    await requireFeature(app.db, req.userId, "weekPlanning", "Veckoplanering");
    const input = parse(generateWeekPlanInputSchema, req.body);
    const householdId = await requireActiveHousehold(app.db, req.userId);

    const [plan] = await app.db
      .insert(schema.weekPlans)
      .values({
        householdId,
        weekStartDate: input.weekStartDate,
        status: "draft",
        generatedBy: "engine",
        notes: input.noteSv ?? null,
      })
      .returning();

    await app.jobQueue.add("GENERATE_WEEK_PLAN", {
      jobType: "GENERATE_WEEK_PLAN",
      weekPlanId: plan!.id,
      householdId,
      userId: req.userId,
      input,
      correlationId: req.correlationId,
    });

    return reply.status(202).send({
      plan,
      message: "Planen genereras – hämta den om en stund via GET /v1/week-plans.",
    });
  });

  app.patch("/v1/week-plans/:id/entries/:entryId", auth, async (req) => {
    const params = req.params as { id: string; entryId: string };
    const [plan] = await app.db
      .select()
      .from(schema.weekPlans)
      .where(eq(schema.weekPlans.id, params.id))
      .limit(1);
    if (!plan) throw errors.notFound("Planen finns inte.");
    await requireMembership(app.db, plan.householdId, req.userId);

    const input = parse(updatePlanEntryInputSchema, req.body);
    const updates: Record<string, unknown> = { ...input };

    // Dynamisk omplanering med förklaring (spec §25)
    if (input.status === "skipped") {
      const [entry] = await app.db
        .select()
        .from(schema.weekPlanEntries)
        .where(eq(schema.weekPlanEntries.id, params.entryId))
        .limit(1);
      if (entry?.recipeId) {
        // Flytta rätten till nästa lediga dag om råvaror bör användas.
        const later = await app.db
          .select()
          .from(schema.weekPlanEntries)
          .where(
            and(
              eq(schema.weekPlanEntries.weekPlanId, params.id),
              gte(schema.weekPlanEntries.date, entry.date),
              eq(schema.weekPlanEntries.status, "planned"),
              sql`${schema.weekPlanEntries.id} <> ${params.entryId}`,
            ),
          )
          .orderBy(schema.weekPlanEntries.date)
          .limit(1);
        if (later[0]) {
          await app.db
            .update(schema.weekPlanEntries)
            .set({
              recipeId: entry.recipeId,
              titleSv: entry.titleSv,
              status: "moved",
              rescheduleReasonSv: `${entry.titleSv} flyttades hit eftersom råvarorna bör användas först.`,
            })
            .where(eq(schema.weekPlanEntries.id, later[0].id));
        }
      }
    }

    const [row] = await app.db
      .update(schema.weekPlanEntries)
      .set(updates)
      .where(
        and(
          eq(schema.weekPlanEntries.id, params.entryId),
          eq(schema.weekPlanEntries.weekPlanId, params.id),
        ),
      )
      .returning();
    if (!row) throw errors.notFound("Planposten finns inte.");

    await emitEvent(app.db, {
      type: "WEEK_PLAN_UPDATED",
      payload: { weekPlanId: params.id, reason: input.status ?? null },
      userId: req.userId,
      householdId: plan.householdId,
      correlationId: req.correlationId,
    });
    return row;
  });

  app.post("/v1/week-plans/:id/activate", auth, async (req) => {
    const { id } = parse(idParamSchema, req.params);
    const [plan] = await app.db
      .select()
      .from(schema.weekPlans)
      .where(eq(schema.weekPlans.id, id))
      .limit(1);
    if (!plan) throw errors.notFound();
    await requireMembership(app.db, plan.householdId, req.userId);
    const [row] = await app.db
      .update(schema.weekPlans)
      .set({ status: "active", updatedAt: new Date() })
      .where(eq(schema.weekPlans.id, id))
      .returning();
    return row;
  });
}
