/**
 * Katalog-gaprapport (docs/32 §4).
 *
 * Byggs ur consent-gatad nollträff-bankning + cook/rating-signaler.
 * Identifierar vad som saknas i receptkatalogen så påfyllningen kan
 * styras mot verklig efterfrågan.
 */

import type { GapReport, GapReportEntry } from "./types.js";

export interface AnalyticsEvent {
  eventName: string;
  occurredAt: Date;
  properties: Record<string, unknown>;
}

export interface RecipeSignal {
  recipeId: string | null;
  titleSv: string | null;
  cookCount: number;
  ratingAverage: number | null;
  ratingCount: number;
  searchQuery?: string | null;
}

export interface GapReportOptions {
  /** Minsta antal nollträffar för att en sökning ska räknas. */
  minMissThreshold?: number;
  /** Hur långt bak i tiden (dagar). */
  lookbackDays?: number;
  /** Max antal entries i rapporten. */
  maxEntries?: number;
}

const DEFAULT_GAP_OPTIONS: Required<GapReportOptions> = {
  minMissThreshold: 2,
  lookbackDays: 30,
  maxEntries: 50,
};

/**
 * Bygg gaprapport ur analytics-events och receptsignaler.
 *
 * @param searchMissEvents productAnalyticsEvents med eventName = "recipe_search_zero_results"
 * @param cookSignals recipe_cooks aggregerade per recept
 * @param ratingSignals recipe_ratings aggregerade per recept
 * @param options Filter och begränsningar
 */
export function buildGapReport(
  searchMissEvents: AnalyticsEvent[],
  cookSignals: RecipeSignal[],
  ratingSignals: RecipeSignal[],
  options: GapReportOptions = {},
): GapReport {
  const opts = { ...DEFAULT_GAP_OPTIONS, ...options };
  const cutoff = new Date();
  cutoff.setDate(cutoff.getDate() - opts.lookbackDays);

  // ── 1. Samla nollträffar ─────────────────────────────────────────────────
  const missMap = new Map<string, { count: number; lastAt: Date; queries: Set<string> }>();

  for (const ev of searchMissEvents) {
    if (ev.occurredAt < cutoff) continue;

    const query = String(ev.properties?.query ?? "").toLowerCase().trim();
    if (!query) continue;

    const ingredientId = ev.properties?.suggestedIngredientId
      ? String(ev.properties.suggestedIngredientId)
      : null;
    const mealType = ev.properties?.mealType
      ? String(ev.properties.mealType)
      : null;

    const key = `${query}::${ingredientId ?? "_"}::${mealType ?? "_"}`;
    const existing = missMap.get(key);
    if (existing) {
      existing.count++;
      existing.queries.add(query);
      if (ev.occurredAt > existing.lastAt) existing.lastAt = ev.occurredAt;
    } else {
      missMap.set(key, {
        count: 1,
        lastAt: ev.occurredAt,
        queries: new Set([query]),
      });
    }
  }

  // ── 2. Filtrera och rangordna ────────────────────────────────────────────
  const entries: GapReportEntry[] = [];

  for (const [key, data] of missMap) {
    if (data.count < opts.minMissThreshold) continue;

    const parts = key.split("::");
    const ingredientId = parts[1] ?? null;
    const mealType = parts[2] ?? null;

    entries.push({
      searchQuery: [...data.queries][0] ?? "",
      missCount: data.count,
      lastMissedAt: data.lastAt,
      suggestedIngredientId: ingredientId === "_" ? null : ingredientId,
      suggestedMealType: mealType === "_" ? null : mealType,
      priority: data.count >= 10 ? "high" : data.count >= 5 ? "medium" : "low",
    });
  }

  // Sortera: high → medium → low, sedan antal
  entries.sort((a, b) => {
    const pOrder = { high: 0, medium: 1, low: 2 };
    if (pOrder[a.priority] !== pOrder[b.priority]) {
      return pOrder[a.priority] - pOrder[b.priority];
    }
    return b.missCount - a.missCount;
  });

  const limitedEntries = entries.slice(0, opts.maxEntries);

  // ── 3. Aggregera topp-saknade ────────────────────────────────────────────
  const ingredientCounts = new Map<string | null, number>();
  const mealTypeCounts = new Map<string | null, number>();

  for (const e of limitedEntries) {
    ingredientCounts.set(e.suggestedIngredientId, (ingredientCounts.get(e.suggestedIngredientId) ?? 0) + e.missCount);
    mealTypeCounts.set(e.suggestedMealType, (mealTypeCounts.get(e.suggestedMealType) ?? 0) + e.missCount);
  }

  const topMissingIngredients = [...ingredientCounts.entries()]
    .sort((a, b) => b[1] - a[1])
    .slice(0, 10)
    .map(([id, count]) => ({ canonicalIngredientId: id as string | null, count }));

  const topMissingMealTypes = [...mealTypeCounts.entries()]
    .sort((a, b) => b[1] - a[1])
    .slice(0, 10)
    .map(([mealType, count]) => ({ mealType: mealType as string | null, count }));

  return {
    generatedAt: new Date(),
    entries: limitedEntries,
    topMissingIngredients,
    topMissingMealTypes,
  };
}

/**
 * Formatera gaprapporten som läsbar text för granskning.
 */
export function formatGapReport(report: GapReport): string {
  const lines: string[] = [
    `# Katalog-gaprapport`,
    `Genererad: ${report.generatedAt.toISOString()}`,
    ``,
    `## Topp-saknade ingredienser`,
    ...report.topMissingIngredients.map(
      (i) => `- ${i.canonicalIngredientId ?? "(okänd)"}: ${i.count} nollträffar`,
    ),
    ``,
    `## Topp-saknade måltidstyper`,
    ...report.topMissingMealTypes.map(
      (m) => `- ${m.mealType ?? "(okänd)"}: ${m.count} nollträffar`,
    ),
    ``,
    `## Detaljer (${report.entries.length} entries)`,
    ...report.entries.map(
      (e) =>
        `- [${e.priority.toUpperCase()}] "${e.searchQuery}" — ${e.missCount} missar, senast ${e.lastMissedAt.toISOString().slice(0, 10)}${e.suggestedIngredientId ? ` (ingrediens: ${e.suggestedIngredientId})` : ""}${e.suggestedMealType ? ` (måltid: ${e.suggestedMealType})` : ""}`,
    ),
  ];
  return lines.join("\n");
}
