import { and, desc, eq, gt, gte, inArray, isNull, lte, sql } from "drizzle-orm";
import { schema } from "@app/database";
import { classifyExpiry } from "@app/inventory-engine";
import type { LocaleContext } from "@app/shared-types";
import { renderProactiveTip } from "../templates/proactive-tips.js";
import { getLocaleContext } from "../locale.js";
import type { WorkerContext } from "../context.js";

/**
 * S2: Proaktiva puffar (opt-in). Skriver till notifications-tabellen, aldrig push.
 *
 * Hårda krav:
 * 1. Dubbelgrind: personalization-samtycke OCH notifications-samtycke granted.
 * 2. Mallbaserad text (12 språk), ingen fri AI-text.
 * 3. Led med nyttan; aldrig skam/övervakning.
 * 4. Max 1 puff/hushåll/dag; ingen upprepning inom 7 dagar.
 * 5. UTC för all datummatte.
 */

const ONE_DAY_MS = 24 * 60 * 60 * 1000;
const SEVEN_DAYS_MS = 7 * ONE_DAY_MS;

interface ExpiringItem {
  itemId: string;
  canonicalIngredientId: string | null;
  displayName: string;
  daysLeft: number | null;
}

interface RecipeMatch {
  recipeId: string;
  titleSv: string;
}

export interface ProcessProactiveTipsOptions {
  nowUtc?: Date;
}

function consentGranted(consents: Array<{ kind: string; status: string }>, kind: string): boolean {
  return consents.some((c) => c.kind === kind && c.status === "granted");
}

/** Hitta alla utgångsnära varor för hushållet (≤2 dagar kvar), sorterade efter brådska. */
async function findExpiringItems(
  ctx: WorkerContext,
  householdId: string,
  now: Date,
): Promise<ExpiringItem[]> {
  const rows = await ctx.db
    .select({
      item: schema.inventoryItems,
      locationType: schema.storageLocations.type,
      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),
        isNull(schema.inventoryItems.depletedAt),
        gt(schema.inventoryItems.quantity, 0),
      ),
    );

  const expiring: ExpiringItem[] = [];
  for (const r of rows) {
    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,
    });
    if (expiry.status === "expiring" && (expiry.daysLeft ?? 99) <= 2) {
      expiring.push({
        itemId: r.item.id,
        canonicalIngredientId: r.item.canonicalIngredientId,
        displayName: r.item.displayName,
        daysLeft: expiry.daysLeft,
      });
    }
  }

  // Prioritera kortast tid kvar.
  expiring.sort((a, b) => (a.daysLeft ?? 99) - (b.daysLeft ?? 99));
  return expiring;
}

/** Hitta ett lämpligt recept som använder ingrediensen och inte lagats nyligen. */
async function findRecipeForIngredient(
  ctx: WorkerContext,
  householdId: string,
  canonicalIngredientId: string | null,
  now: Date,
): Promise<RecipeMatch | null> {
  if (!canonicalIngredientId) return null;

  const recentCooks = await ctx.db
    .select({ recipeId: schema.recipeCooks.recipeId })
    .from(schema.recipeCooks)
    .where(
      and(
        eq(schema.recipeCooks.householdId, householdId),
        gte(schema.recipeCooks.cookedAt, new Date(now.getTime() - SEVEN_DAYS_MS)),
      ),
    );
  const recentlyCookedIds = new Set(recentCooks.map((r) => r.recipeId));

  const matches = await ctx.db
    .select({
      recipeId: schema.recipeIngredients.recipeId,
      titleSv: schema.recipes.titleSv,
    })
    .from(schema.recipeIngredients)
    .innerJoin(schema.recipes, eq(schema.recipeIngredients.recipeId, schema.recipes.id))
    .where(
      and(
        eq(schema.recipeIngredients.canonicalIngredientId, canonicalIngredientId),
        eq(schema.recipes.verificationStatus, "verified"),
      ),
    )
    .orderBy(desc(schema.recipes.ratingAverage))
    .limit(20);

  for (const m of matches) {
    if (!recentlyCookedIds.has(m.recipeId)) {
      return { recipeId: m.recipeId, titleSv: m.titleSv };
    }
  }
  return matches[0] ?? null;
}

/** Max 1 puff/hushåll/dag. */
async function householdReceivedTipToday(
  ctx: WorkerContext,
  householdId: string,
  since: Date,
): Promise<boolean> {
  const members = await ctx.db
    .select({ userId: schema.householdMembers.userId })
    .from(schema.householdMembers)
    .where(eq(schema.householdMembers.householdId, householdId));
  if (members.length === 0) return false;

  const userIds = members.map((m) => m.userId);
  if (userIds.length === 0) return false;
  const [recent] = await ctx.db
    .select({ id: schema.notifications.id })
    .from(schema.notifications)
    .where(
      and(
        eq(schema.notifications.type, "proactive_tip"),
        inArray(schema.notifications.userId, userIds),
        gte(schema.notifications.createdAt, since),
      ),
    )
    .limit(1);
  return !!recent;
}

/** Ingen upprepning av samma vara+recept inom 7 dagar. */
async function isDuplicateTip(
  ctx: WorkerContext,
  userId: string,
  canonicalIngredientId: string | null,
  recipeId: string,
  since: Date,
): Promise<boolean> {
  const dedupKey = `${canonicalIngredientId ?? ""}:${recipeId}`;
  const rows = await ctx.db
    .select({ data: schema.notifications.data })
    .from(schema.notifications)
    .where(
      and(
        eq(schema.notifications.userId, userId),
        eq(schema.notifications.type, "proactive_tip"),
        gte(schema.notifications.createdAt, since),
      ),
    );
  return rows.some((r) => {
    if (!r.data || typeof r.data !== "object") return false;
    const value = (r.data as Record<string, unknown>).dedupKey;
    return String(value) === dedupKey;
  });
}

async function getHouseholdConsents(
  ctx: WorkerContext,
  userId: string,
): Promise<Array<{ kind: string; status: string }>> {
  return ctx.db
    .select({ kind: schema.userConsents.kind, status: schema.userConsents.status })
    .from(schema.userConsents)
    .where(eq(schema.userConsents.userId, userId));
}

export async function processProactiveTips(
  ctx: WorkerContext,
  options: ProcessProactiveTipsOptions = {},
): Promise<number> {
  const now = options.nowUtc ?? new Date();
  const todayStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
  const sevenDaysAgo = new Date(now.getTime() - SEVEN_DAYS_MS);

  const households = await ctx.db.select({ id: schema.households.id }).from(schema.households);

  let created = 0;

  for (const household of households) {
    // Frekvens: max 1 puff/hushåll/dag.
    if (await householdReceivedTipToday(ctx, household.id, todayStart)) {
      continue;
    }

    // Hämta alla utgångsnära varor sorterade efter brådska.
    const items = await findExpiringItems(ctx, household.id, now);
    if (items.length === 0) continue;

    // Välj högst en berättigad mottagare per hushåll per dag.
    const members = await ctx.db
      .select({ userId: schema.householdMembers.userId })
      .from(schema.householdMembers)
      .where(eq(schema.householdMembers.householdId, household.id));

    let recipientId: string | null = null;
    for (const member of members) {
      const consents = await getHouseholdConsents(ctx, member.userId);
      const hasPersonalization = consentGranted(consents, "personalization");
      const hasNotifications = consentGranted(consents, "notifications");
      if (hasPersonalization && hasNotifications) {
        recipientId = member.userId;
        break;
      }
    }
    if (!recipientId) continue;

    // Försök varje kandidat i brådskande ordning tills en kvalificerar.
    let emitted = false;
    for (const item of items) {
      const recipe = await findRecipeForIngredient(
        ctx,
        household.id,
        item.canonicalIngredientId,
        now,
      );
      if (!recipe) continue;

      // Ingen upprepning av samma vara+recept inom 7 dagar.
      if (
        await isDuplicateTip(
          ctx,
          recipientId,
          item.canonicalIngredientId,
          recipe.recipeId,
          sevenDaysAgo,
        )
      ) {
        continue;
      }

      const locale = await getLocaleContext(ctx, recipientId);
      const rendered = renderProactiveTip("expiring_ingredient", locale, {
        ingredientName: item.displayName,
        recipeTitle: recipe.titleSv,
        recipeId: recipe.recipeId,
      });
      if (!rendered) continue;

      await ctx.db.insert(schema.notifications).values({
        userId: recipientId,
        type: "proactive_tip",
        titleSv: rendered.title,
        bodySv: rendered.body,
        data: {
          itemId: item.itemId,
          recipeId: recipe.recipeId,
          ingredientName: item.displayName,
          canonicalIngredientId: item.canonicalIngredientId,
          dedupKey: `${item.canonicalIngredientId ?? ""}:${recipe.recipeId}`,
        },
        templateKey: rendered.templateKey,
        variables: rendered.variables,
        locale: locale.languageTag,
        scheduledFor: now,
        createdAt: now,
      });
      emitted = true;
      created++;
      break;
    }

    // Om ingen kandidat kvalificerade går vi vidare till nästa hushåll.
    if (!emitted) continue;
  }

  return created;
}
