import type { FastifyInstance } from "fastify";
import { and, eq, gte, inArray, sql } from "drizzle-orm";
import { schema } from "@app/database";
import { z } from "zod";
import { buildReconciliationCandidates, classifyExpiry, computeTrust } from "@app/inventory-engine";
import { errors, parse } from "../lib/errors.js";
import { getActiveDecayProfile, requireActiveHousehold, requireMembership, trackProductAnalytics } from "../lib/helpers.js";
import {
  reconciliationResolveInputSchema,
  reconciliationStartInputSchema,
} from "@app/validation";
import { inventoryReconciliationCompleted, inventoryReconciliationStarted } from "@app/analytics";

/** Quick Reconciliation (Fas 2 §5.4) */
export async function reconciliationRoutes(app: FastifyInstance) {
  const auth = { preHandler: [app.authenticate] };

  app.post("/v1/reconciliations/start", auth, async (req) => {
    const householdId = await requireActiveHousehold(app.db, req.userId);
    await requireMembership(app.db, householdId, req.userId);
    const input = parse(reconciliationStartInputSchema, req.body);

    const decayProfile = await getActiveDecayProfile(app.db);

    const items = await app.db
      .select({
        item: schema.inventoryItems,
        locationType: schema.storageLocations.type,
        locationName: schema.storageLocations.name,
        shelfLife: schema.canonicalIngredients.shelfLifeGuidance,
      })
      .from(schema.inventoryItems)
      .innerJoin(
        schema.storageLocations,
        eq(schema.inventoryItems.storageLocationId, schema.storageLocations.id),
      )
      .leftJoin(
        schema.canonicalIngredients,
        eq(schema.inventoryItems.canonicalIngredientId, schema.canonicalIngredients.id),
      )
      .where(
        and(
          eq(schema.inventoryItems.householdId, householdId),
          sql`${schema.inventoryItems.depletedAt} IS NULL`,
          sql`${schema.inventoryItems.quantity} > 0`,
        ),
      );

    const itemIds = items.map((r) => r.item.id);

    // Ingredienser i planerade recept närmaste 7 dagarna
    const plannedRecipeIngredientIds = new Map<string, string[]>();
    const upcomingEntries = await app.db
      .select({ recipeId: schema.weekPlanEntries.recipeId, date: schema.weekPlanEntries.date })
      .from(schema.weekPlanEntries)
      .innerJoin(schema.weekPlans, eq(schema.weekPlanEntries.weekPlanId, schema.weekPlans.id))
      .where(
        and(
          eq(schema.weekPlans.householdId, householdId),
          sql`${schema.weekPlanEntries.date} >= CURRENT_DATE`,
          sql`${schema.weekPlanEntries.date} <= CURRENT_DATE + INTERVAL '7 days'`,
        ),
      );

    if (upcomingEntries.length > 0) {
      const recipeIds = [...new Set(upcomingEntries.map((m) => m.recipeId).filter(Boolean))] as string[];
      if (recipeIds.length > 0) {
        const ingredients = await app.db
          .select({ recipeId: schema.recipeIngredients.recipeId, canonicalId: schema.recipeIngredients.canonicalIngredientId })
          .from(schema.recipeIngredients)
          .where(inArray(schema.recipeIngredients.recipeId, recipeIds));
        for (const ing of ingredients) {
          if (!ing.canonicalId) continue;
          const list = plannedRecipeIngredientIds.get(ing.canonicalId) ?? [];
          if (!list.includes(ing.recipeId)) list.push(ing.recipeId);
          plannedRecipeIngredientIds.set(ing.canonicalId, list);
        }
      }
    }

    const priceMinorByItemId = new Map<string, number>();
    const dailyConsumptionRate = new Map<string, number>();
    const daysLeftByItemId = new Map<string, number | null>();

    for (const r of items) {
      const expiry = classifyExpiry({
        bestBeforeDate: r.item.bestBeforeDate,
        useByDate: r.item.useByDate,
        openedAt: r.item.openedAt,
        frozenAt: r.item.frozenAt,
        thawedAt: r.item.thawedAt,
        purchasedAt: r.item.purchasedAt,
        storageLocationType: r.locationType,
        shelfLifeGuidance: r.shelfLife,
      });
      daysLeftByItemId.set(r.item.id, expiry.daysLeft);

      if (r.item.priceMinor != null) {
        priceMinorByItemId.set(r.item.id, r.item.priceMinor);
      }

      // Enkel heuristik: senaste 30 dagarnas genomsnittliga dagliga förbrukning
      const thirtyDaysAgo = new Date(Date.now() - 30 * 86_400_000);
      const txAgg = await app.db
        .select({
          total: sql<number>`COALESCE(SUM(ABS(${schema.inventoryTransactions.quantityDelta})), 0)`,
          days: sql<number>`GREATEST(1, COUNT(DISTINCT DATE(${schema.inventoryTransactions.createdAt})))`,
        })
        .from(schema.inventoryTransactions)
        .where(
          and(
            eq(schema.inventoryTransactions.inventoryItemId, r.item.id),
            eq(schema.inventoryTransactions.type, "consume"),
            sql`${schema.inventoryTransactions.createdAt} >= ${thirtyDaysAgo}`,
          ),
        );
      const rate = Number(txAgg[0]?.total ?? 0) / Number(txAgg[0]?.days ?? 1);
      if (rate > 0) dailyConsumptionRate.set(r.item.id, rate);
    }

    const mappedItems = items.map((r) => {
      const trust = computeTrust(
        {
          confidence: r.item.confidence,
          verifiedByUser: r.item.verifiedByUser,
          lastVerifiedAt: r.item.lastVerifiedAt,
          quantity: r.item.quantity,
          updatedAt: r.item.updatedAt,
        },
        new Date(),
        decayProfile,
      );
      return {
        id: r.item.id,
        displayName: r.item.displayName,
        quantity: r.item.quantity,
        unit: r.item.unit,
        locationName: r.locationName,
        confidence: r.item.confidence,
        verifiedByUser: r.item.verifiedByUser,
        lastVerifiedAt: r.item.lastVerifiedAt,
        updatedAt: r.item.updatedAt,
        depletedAt: r.item.depletedAt,
        canonicalIngredientId: r.item.canonicalIngredientId,
        trustState: trust.state,
      };
    });

    const candidates = buildReconciliationCandidates(
      mappedItems,
      {
        plannedRecipeIngredientIds,
        daysLeftByItemId,
        dailyConsumptionRate,
        priceMinorByItemId,
      },
      new Date(),
      input.maxItems ?? 15,
    );

    await trackProductAnalytics(
      app.db,
      req.userId,
      inventoryReconciliationStarted({
        householdId,
        properties: { candidateCount: candidates.length },
      }),
    );

    return {
      candidates: candidates.map((c) => ({
        itemId: c.itemId,
        displayName: c.displayName,
        quantity: c.quantity,
        unit: c.unit,
        locationName: c.locationName,
        reasons: c.reasons,
        suggestedAction: c.suggestedAction,
        suggestedQuantity: c.suggestedQuantity,
      })),
    };
  });

  app.post("/v1/reconciliations/items/:itemId/resolve", auth, async (req) => {
    const householdId = await requireActiveHousehold(app.db, req.userId);
    await requireMembership(app.db, householdId, req.userId);

    const params = z.object({ itemId: z.uuid() }).parse(req.params);
    const input = parse(reconciliationResolveInputSchema, req.body);

    const [item] = await app.db
      .select()
      .from(schema.inventoryItems)
      .where(
        and(
          eq(schema.inventoryItems.id, params.itemId),
          eq(schema.inventoryItems.householdId, householdId),
        ),
      )
      .limit(1);
    if (!item) throw errors.notFound("Varan finns inte.");

    const now = new Date();
    const newQuantity = input.quantity;
    const quantityChange = newQuantity != null ? newQuantity - item.quantity : 0;

    const update: Partial<typeof schema.inventoryItems.$inferInsert> = {
      updatedAt: now,
    };

    if (input.action === "exists") {
      update.verifiedByUser = true;
      update.lastVerifiedAt = now;
      update.depletedAt = null;
      if (newQuantity != null) update.quantity = newQuantity;
    } else if (input.action === "depleted") {
      update.quantity = 0;
      update.depletedAt = now;
    } else {
      // uncertain: bara registrera en adjustment om användaren justerat mängd
      if (newQuantity != null) update.quantity = newQuantity;
    }

    const [updated] = await app.db
      .update(schema.inventoryItems)
      .set(update)
      .where(eq(schema.inventoryItems.id, params.itemId))
      .returning();
    if (!updated) throw errors.internal("Kunde inte uppdatera varan.");

    if (input.action === "depleted") {
      await app.db.insert(schema.inventoryTransactions).values({
        inventoryItemId: params.itemId,
        householdId,
        actorUserId: req.userId,
        type: "correction",
        quantityDelta: -item.quantity,
        unit: item.unit,
        note: input.note,
      });
    } else if (input.action === "exists" || quantityChange !== 0) {
      await app.db.insert(schema.inventoryTransactions).values({
        inventoryItemId: params.itemId,
        householdId,
        actorUserId: req.userId,
        type: input.action === "exists" ? "correction" : "adjust",
        quantityDelta: quantityChange,
        unit: item.unit,
        note: input.note,
      });
    }

    await trackProductAnalytics(
      app.db,
      req.userId,
      inventoryReconciliationCompleted({
        householdId,
        properties: { itemId: params.itemId, action: input.action, hadAdjustment: quantityChange !== 0 },
      }),
    );

    return {
      itemId: params.itemId,
      action: input.action,
      quantity: updated.quantity,
      verifiedByUser: updated.verifiedByUser,
    };
  });
}
