/**
 * DB-Layer: LyraMemory
 *
 * Strukturierte persistente User-Erinnerungen für den Lyra-Coach.
 * Enthält Art-9-Gesundheitsdaten — kein direkter Zugriff außer über diese Funktionen.
 *
 * Constraints:
 * - Max MAX_MEMORIES_PER_USER pro User (Datenminimierung + System-Prompt-Budget)
 * - upsertMemory: ähnlicher Content (Substring) → Update statt Insert
 */
import { usePrisma } from "../utils/prisma";
import type { LyraMemoryType } from "../generated/prisma";

export type { LyraMemoryType };

export interface LyraMemoryRow {
  id: string;
  userId: string;
  type: LyraMemoryType;
  content: string;
  confidence: number;
  source: string | null;
  createdAt: Date;
  updatedAt: Date;
  lastReferencedAt: Date | null;
}

const MAX_MEMORIES_PER_USER = 30;
const LOG = "[lyra-memory]";

/**
 * Alle Memories eines Users, sortiert nach Relevanz (zuletzt referenziert → neueste).
 */
export async function getMemoriesForUser(
  userId: string,
): Promise<LyraMemoryRow[]> {
  const db = usePrisma();
  return db.lyraMemory.findMany({
    where: { userId },
    orderBy: [
      { lastReferencedAt: { sort: "desc", nulls: "last" } },
      { createdAt: "desc" },
    ],
  }) as Promise<LyraMemoryRow[]>;
}

/**
 * Upsert: wenn Content-Substring eines bestehenden Eintrags gleichen Typs
 * bereits vorhanden ist → update confidence + source. Sonst insert.
 * Hält Max-Constraint ein: bei > MAX_MEMORIES_PER_USER werden älteste
 * mit niedrigster confidence gelöscht.
 */
export async function upsertMemory(
  userId: string,
  type: LyraMemoryType,
  content: string,
  source?: string,
  confidence = 0.7,
): Promise<LyraMemoryRow> {
  const db = usePrisma();
  const trimmedContent = content.slice(0, 500).trim();

  // Similarity-Check: existierende Memories gleichen Typs laden
  const existing = await db.lyraMemory.findMany({
    where: { userId, type },
    select: { id: true, content: true, confidence: true },
  });

  // Substring-Match (case-insensitive)
  const contentLower = trimmedContent.toLowerCase();
  const match = existing.find((m) => {
    const mLower = m.content.toLowerCase();
    return (
      mLower.includes(contentLower) ||
      contentLower.includes(mLower) ||
      // 70%-Overlap-Heuristik für kurze Strings
      (contentLower.length > 20 &&
        mLower.length > 20 &&
        levenshteinSimilarity(contentLower, mLower) > 0.7)
    );
  });

  let result: LyraMemoryRow;

  if (match) {
    // Update: nimm höhere confidence + neuen Source
    const newConfidence = Math.max(match.confidence, confidence);
    result = (await db.lyraMemory.update({
      where: { id: match.id },
      data: {
        content: trimmedContent,
        confidence: newConfidence,
        source: source ?? undefined,
        updatedAt: new Date(),
      },
    })) as LyraMemoryRow;
    console.log(
      `${LOG} updated memory ${match.id} (type=${type}, conf=${newConfidence})`,
    );
  } else {
    result = (await db.lyraMemory.create({
      data: {
        userId,
        type,
        content: trimmedContent,
        confidence,
        source: source ?? null,
      },
    })) as LyraMemoryRow;
    console.log(
      `${LOG} created memory ${result.id} (type=${type}, conf=${confidence})`,
    );

    // Max-Constraint enforzen
    await enforceMaxMemories(userId);
  }

  return result;
}

/**
 * Markiert Memories als zuletzt referenziert (in System-Prompt injiziert).
 * Fire-and-forget geeignet — wirft nicht.
 */
export async function markReferenced(memoryIds: string[]): Promise<void> {
  if (!memoryIds.length) return;
  const db = usePrisma();
  try {
    await db.lyraMemory.updateMany({
      where: { id: { in: memoryIds } },
      data: { lastReferencedAt: new Date() },
    });
    console.log(`${LOG} markReferenced: ${memoryIds.length} memories`);
  } catch (e) {
    console.error(`${LOG} markReferenced error:`, e);
  }
}

/**
 * User-seitiges Delete (V2.5-ready — Endpoint kommt später).
 */
export async function deleteMemoryById(
  userId: string,
  memoryId: string,
): Promise<void> {
  const db = usePrisma();
  await db.lyraMemory.deleteMany({
    where: { id: memoryId, userId },
  });
  console.log(`${LOG} deleted memory ${memoryId} for user ${userId}`);
}

// ── Interne Helpers ──────────────────────────────────────────────────────────

async function enforceMaxMemories(userId: string): Promise<void> {
  const db = usePrisma();
  const total = await db.lyraMemory.count({ where: { userId } });
  if (total <= MAX_MEMORIES_PER_USER) return;

  const overflow = total - MAX_MEMORIES_PER_USER;
  // Älteste mit niedrigster confidence zuerst löschen
  const candidates = await db.lyraMemory.findMany({
    where: { userId },
    orderBy: [{ confidence: "asc" }, { createdAt: "asc" }],
    take: overflow,
    select: { id: true },
  });
  const ids = candidates.map((c) => c.id);
  await db.lyraMemory.deleteMany({ where: { id: { in: ids } } });
  console.log(`${LOG} enforceMax: deleted ${ids.length} old memories`);
}

/**
 * Einfache Levenshtein-basierte Ähnlichkeit (0.0-1.0).
 * Nur für kurze Strings — O(n*m) ist ok für max 500 chars.
 */
function levenshteinSimilarity(a: string, b: string): number {
  if (a === b) return 1;
  const la = a.length;
  const lb = b.length;
  const dp: number[][] = Array.from({ length: la + 1 }, (_, i) =>
    Array.from({ length: lb + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)),
  );
  for (let i = 1; i <= la; i++) {
    for (let j = 1; j <= lb; j++) {
      dp[i][j] =
        a[i - 1] === b[j - 1]
          ? dp[i - 1][j - 1]
          : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
    }
  }
  return 1 - dp[la][lb] / Math.max(la, lb);
}
