import { randomUUID } from "node:crypto";
import { and, eq, getTableName, inArray, ne, or, sql } from "drizzle-orm";
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
import { deleteUserAnalyticsEvents } from "./analytics-gdpr.js";
import * as schema from "./schema/index.js";

type Database = NodePgDatabase<typeof schema>;

export type ErasureLogEntry = {
  table: string;
  action: "delete" | "anonymize" | "pseudonymize" | "retain_pseudonym";
  count: number;
};

export type ErasurePlan = {
  /** S3 URLs/keys that must be purged from object storage. */
  imageReferences: string[];
  /** Households that will be hard-deleted because the user was the sole member. */
  householdsToDelete: string[];
};

const RETENTION_YEARS = 7;

/** Build the plan without mutating anything. */
export async function buildErasurePlan(db: Database, userId: string): Promise<ErasurePlan> {
  const imageReferences: string[] = [];

  // --- Personal images (always deleted) ---
  const correctionImages = await db
    .select({ key: schema.aiCorrections.imageS3Key })
    .from(schema.aiCorrections)
    .where(eq(schema.aiCorrections.userId, userId));
  for (const row of correctionImages) {
    if (row.key) imageReferences.push(row.key);
  }

  const bankImages = await db
    .select({ key: schema.aiTrainingBank.imageS3Key })
    .from(schema.aiTrainingBank)
    .innerJoin(schema.aiCorrections, eq(schema.aiTrainingBank.correctionId, schema.aiCorrections.id))
    .where(eq(schema.aiCorrections.userId, userId));
  for (const row of bankImages) {
    if (row.key) imageReferences.push(row.key);
  }

  const scanRows = await db
    .select({ keys: schema.scanJobs.s3Keys })
    .from(schema.scanJobs)
    .where(eq(schema.scanJobs.userId, userId));
  for (const row of scanRows) {
    for (const key of row.keys ?? []) {
      if (key) imageReferences.push(key);
    }
  }

  const memoryPhotos = await db
    .select({ url: schema.foodMemories.photoUrl })
    .from(schema.foodMemories)
    .where(eq(schema.foodMemories.userId, userId));
  for (const row of memoryPhotos) {
    if (row.url) imageReferences.push(row.url);
  }

  // --- Households ---
  const householdRows = await db
    .select({ householdId: schema.householdMembers.householdId })
    .from(schema.householdMembers)
    .where(eq(schema.householdMembers.userId, userId));

  const householdsToDelete: string[] = [];
  for (const { householdId } of householdRows) {
    const memberCount = await db
      .select({ count: sql<number>`count(*)::int` })
      .from(schema.householdMembers)
      .where(eq(schema.householdMembers.householdId, householdId));
    if (Number(memberCount[0]?.count ?? 0) <= 1) {
      householdsToDelete.push(householdId);
    }
  }

  // Images tied to households we are about to delete.
  if (householdsToDelete.length > 0) {
    const receiptImages = await db
      .select({ url: schema.receipts.imageUrl })
      .from(schema.receipts)
      .where(inArray(schema.receipts.householdId, householdsToDelete));
    for (const row of receiptImages) {
      if (row.url) imageReferences.push(row.url);
    }

    const householdMemoryPhotos = await db
      .select({ url: schema.foodMemories.photoUrl })
      .from(schema.foodMemories)
      .where(inArray(schema.foodMemories.householdId, householdsToDelete));
    for (const row of householdMemoryPhotos) {
      if (row.url) imageReferences.push(row.url);
    }
  }

  // --- Draft/private recipe images (public recipes stay but are anonymized) ---
  const draftRecipeImages = await db
    .select({ urls: schema.recipes.imageUrls })
    .from(schema.recipes)
    .where(
      and(
        eq(schema.recipes.creatorUserId, userId),
        ne(schema.recipes.status, "published"),
      ),
    );
  for (const row of draftRecipeImages) {
    for (const url of row.urls ?? []) {
      if (url) imageReferences.push(url);
    }
  }

  return {
    imageReferences: [...new Set(imageReferences)],
    householdsToDelete,
  };
}

export async function eraseUser(db: Database, userId: string): Promise<ErasureLogEntry[]> {
  const plan = await buildErasurePlan(db, userId);
  const log: ErasureLogEntry[] = [];

  // ---- 1. Auth / tokens / consents / idempotency / push / notifications (hard delete) ----
  const credDel = await db
    .delete(schema.userCredentials)
    .where(eq(schema.userCredentials.userId, userId))
    .returning({ userId: schema.userCredentials.userId });
  if (credDel.length) log.push({ table: getTableName(schema.userCredentials), action: "delete", count: credDel.length });

  const adminTotpDel = await db
    .delete(schema.adminTotp)
    .where(eq(schema.adminTotp.userId, userId))
    .returning({ userId: schema.adminTotp.userId });
  if (adminTotpDel.length) log.push({ table: getTableName(schema.adminTotp), action: "delete", count: adminTotpDel.length });

  const emailTokensDel = await db
    .delete(schema.emailVerificationTokens)
    .where(eq(schema.emailVerificationTokens.userId, userId))
    .returning({ id: schema.emailVerificationTokens.id });
  if (emailTokensDel.length) log.push({ table: getTableName(schema.emailVerificationTokens), action: "delete", count: emailTokensDel.length });

  const pwTokensDel = await db
    .delete(schema.passwordResetTokens)
    .where(eq(schema.passwordResetTokens.userId, userId))
    .returning({ id: schema.passwordResetTokens.id });
  if (pwTokensDel.length) log.push({ table: getTableName(schema.passwordResetTokens), action: "delete", count: pwTokensDel.length });

  const refreshDel = await db
    .delete(schema.refreshTokens)
    .where(eq(schema.refreshTokens.userId, userId))
    .returning({ id: schema.refreshTokens.id });
  if (refreshDel.length) log.push({ table: getTableName(schema.refreshTokens), action: "delete", count: refreshDel.length });

  const consentsDel = await db
    .delete(schema.userConsents)
    .where(eq(schema.userConsents.userId, userId))
    .returning({ userId: schema.userConsents.userId });
  if (consentsDel.length) log.push({ table: getTableName(schema.userConsents), action: "delete", count: consentsDel.length });

  const idemDel = await db
    .delete(schema.idempotencyKeys)
    .where(eq(schema.idempotencyKeys.userId, userId))
    .returning({ key: schema.idempotencyKeys.key });
  if (idemDel.length) log.push({ table: getTableName(schema.idempotencyKeys), action: "delete", count: idemDel.length });

  const pushDel = await db
    .delete(schema.pushTokens)
    .where(eq(schema.pushTokens.userId, userId))
    .returning({ token: schema.pushTokens.token });
  if (pushDel.length) log.push({ table: getTableName(schema.pushTokens), action: "delete", count: pushDel.length });

  const notifDel = await db
    .delete(schema.notifications)
    .where(eq(schema.notifications.userId, userId))
    .returning({ id: schema.notifications.id });
  if (notifDel.length) log.push({ table: getTableName(schema.notifications), action: "delete", count: notifDel.length });

  const feedbackDel = await db
    .delete(schema.feedback)
    .where(eq(schema.feedback.userId, userId))
    .returning({ id: schema.feedback.id });
  if (feedbackDel.length) log.push({ table: getTableName(schema.feedback), action: "delete", count: feedbackDel.length });

  // ---- 2. Recipes ----
  const deletedRecipes = await db
    .delete(schema.recipes)
    .where(
      and(
        eq(schema.recipes.creatorUserId, userId),
        ne(schema.recipes.status, "published"),
      ),
    )
    .returning({ id: schema.recipes.id });
  if (deletedRecipes.length) log.push({ table: getTableName(schema.recipes), action: "delete", count: deletedRecipes.length });

  const anonymizedRecipes = await db
    .update(schema.recipes)
    .set({ creatorUserId: null, creatorDisplayName: null, updatedAt: new Date() })
    .where(
      and(
        eq(schema.recipes.creatorUserId, userId),
        eq(schema.recipes.status, "published"),
      ),
    )
    .returning({ id: schema.recipes.id });
  if (anonymizedRecipes.length) log.push({ table: getTableName(schema.recipes), action: "anonymize", count: anonymizedRecipes.length });

  const ratingsDel = await db
    .delete(schema.recipeRatings)
    .where(eq(schema.recipeRatings.userId, userId))
    .returning({ id: schema.recipeRatings.id });
  if (ratingsDel.length) log.push({ table: getTableName(schema.recipeRatings), action: "delete", count: ratingsDel.length });

  const favoritesDel = await db
    .delete(schema.recipeFavorites)
    .where(eq(schema.recipeFavorites.userId, userId))
    .returning({ recipeId: schema.recipeFavorites.recipeId });
  if (favoritesDel.length) log.push({ table: getTableName(schema.recipeFavorites), action: "delete", count: favoritesDel.length });

  const cooksDel = await db
    .delete(schema.recipeCooks)
    .where(eq(schema.recipeCooks.userId, userId))
    .returning({ id: schema.recipeCooks.id });
  if (cooksDel.length) log.push({ table: getTableName(schema.recipeCooks), action: "delete", count: cooksDel.length });

  const creatorStatsDel = await db
    .delete(schema.creatorStats)
    .where(eq(schema.creatorStats.userId, userId))
    .returning({ userId: schema.creatorStats.userId });
  if (creatorStatsDel.length) log.push({ table: getTableName(schema.creatorStats), action: "delete", count: creatorStatsDel.length });

  const followsDel = await db
    .delete(schema.creatorFollows)
    .where(
      or(eq(schema.creatorFollows.followerUserId, userId), eq(schema.creatorFollows.creatorUserId, userId)),
    )
    .returning({ followerUserId: schema.creatorFollows.followerUserId });
  if (followsDel.length) log.push({ table: getTableName(schema.creatorFollows), action: "delete", count: followsDel.length });

  // ---- 3. Personal content ----
  const mealsDel = await db
    .delete(schema.meals)
    .where(eq(schema.meals.userId, userId))
    .returning({ id: schema.meals.id });
  if (mealsDel.length) log.push({ table: getTableName(schema.meals), action: "delete", count: mealsDel.length });

  const foodMemDel = await db
    .delete(schema.foodMemories)
    .where(eq(schema.foodMemories.userId, userId))
    .returning({ id: schema.foodMemories.id });
  if (foodMemDel.length) log.push({ table: getTableName(schema.foodMemories), action: "delete", count: foodMemDel.length });

  const tasteDel = await db
    .delete(schema.tasteSignals)
    .where(eq(schema.tasteSignals.userId, userId))
    .returning({ id: schema.tasteSignals.id });
  if (tasteDel.length) log.push({ table: getTableName(schema.tasteSignals), action: "delete", count: tasteDel.length });

  const memoryDel = await db
    .delete(schema.memoryItems)
    .where(eq(schema.memoryItems.userId, userId))
    .returning({ id: schema.memoryItems.id });
  if (memoryDel.length) log.push({ table: getTableName(schema.memoryItems), action: "delete", count: memoryDel.length });

  // ---- 4. AI / scans / training ----
  const aiCorrectionsDel = await db
    .delete(schema.aiCorrections)
    .where(eq(schema.aiCorrections.userId, userId))
    .returning({ id: schema.aiCorrections.id });
  if (aiCorrectionsDel.length) log.push({ table: getTableName(schema.aiCorrections), action: "delete", count: aiCorrectionsDel.length });

  const scanJobsDel = await db
    .delete(schema.scanJobs)
    .where(eq(schema.scanJobs.userId, userId))
    .returning({ id: schema.scanJobs.id });
  if (scanJobsDel.length) log.push({ table: getTableName(schema.scanJobs), action: "delete", count: scanJobsDel.length });

  const aiUsageDel = await db
    .delete(schema.aiUsageCounters)
    .where(eq(schema.aiUsageCounters.userId, userId))
    .returning({ userId: schema.aiUsageCounters.userId });
  if (aiUsageDel.length) log.push({ table: getTableName(schema.aiUsageCounters), action: "delete", count: aiUsageDel.length });

  const trialsDel = await db
    .delete(schema.trials)
    .where(eq(schema.trials.userId, userId))
    .returning({ userId: schema.trials.userId });
  if (trialsDel.length) log.push({ table: getTableName(schema.trials), action: "delete", count: trialsDel.length });

  // ---- 5. Financial / subscriptions (pseudonymize) ----
  const subCountRow = await db
    .select({ count: sql<number>`count(*)::int` })
    .from(schema.subscriptions)
    .where(eq(schema.subscriptions.userId, userId));
  const txCountRow = await db
    .select({ count: sql<number>`count(*)::int` })
    .from(schema.storeTransactions)
    .where(eq(schema.storeTransactions.userId, userId));
  const hasFinancialRecords = Number(subCountRow[0]?.count ?? 0) > 0 || Number(txCountRow[0]?.count ?? 0) > 0;

  if (hasFinancialRecords) {
    const pseudonym = randomUUID();
    const retentionUntil = new Date();
    retentionUntil.setFullYear(retentionUntil.getFullYear() + RETENTION_YEARS);

    await db.insert(schema.gdprRetentionPseudonyms).values({
      userId,
      pseudonym,
      retentionUntil,
    });

    const subPseud = await db
      .update(schema.subscriptions)
      .set({ userId: null, pseudonym, householdId: null, updatedAt: new Date() })
      .where(eq(schema.subscriptions.userId, userId))
      .returning({ id: schema.subscriptions.id });
    if (subPseud.length) log.push({ table: getTableName(schema.subscriptions), action: "pseudonymize", count: subPseud.length });

    const storePseud = await db
      .update(schema.storeTransactions)
      .set({
        userId: null,
        pseudonym,
        rawPayload: sql`jsonb_strip_nulls(jsonb_build_object('transaction_id', ${schema.storeTransactions.transactionId}, 'product_id', ${schema.storeTransactions.productId}, 'provider', ${schema.storeTransactions.provider}))`,
      })
      .where(eq(schema.storeTransactions.userId, userId))
      .returning({ id: schema.storeTransactions.id });
    if (storePseud.length) log.push({ table: getTableName(schema.storeTransactions), action: "pseudonymize", count: storePseud.length });

    const eventPseud = await db
      .update(schema.subscriptionEvents)
      .set({ userId: null, pseudonym, payload: sql`jsonb_build_object('anonymized', true)` })
      .where(eq(schema.subscriptionEvents.userId, userId))
      .returning({ id: schema.subscriptionEvents.id });
    if (eventPseud.length) log.push({ table: getTableName(schema.subscriptionEvents), action: "pseudonymize", count: eventPseud.length });
  }

  // ---- 6. Audit logs (anonymize actor, keep for retention) ----
  const auditAnon = await db
    .update(schema.auditLogs)
    .set({ actorUserId: null, ip: null, metadata: sql`jsonb_build_object('anonymized', true)` })
    .where(eq(schema.auditLogs.actorUserId, userId))
    .returning({ id: schema.auditLogs.id });
  if (auditAnon.length) log.push({ table: getTableName(schema.auditLogs), action: "anonymize", count: auditAnon.length });

  // ---- 7. Domain events (anonymize) ----
  const domainAnon = await db
    .update(schema.domainEvents)
    .set({ userId: null, payload: sql`jsonb_build_object('anonymized', true)` })
    .where(eq(schema.domainEvents.userId, userId))
    .returning({ id: schema.domainEvents.id });
  if (domainAnon.length) log.push({ table: getTableName(schema.domainEvents), action: "anonymize", count: domainAnon.length });

  // ---- 8. Households ----
  // Remove memberships first; remember surviving households for receipt anonymization.
  const survivingHouseholds = await db
    .delete(schema.householdMembers)
    .where(eq(schema.householdMembers.userId, userId))
    .returning({ householdId: schema.householdMembers.householdId });

  if (plan.householdsToDelete.length > 0) {
    const hhDel = await db
      .delete(schema.households)
      .where(inArray(schema.households.id, plan.householdsToDelete))
      .returning({ id: schema.households.id });
    if (hhDel.length) log.push({ table: getTableName(schema.households), action: "delete", count: hhDel.length });
  }

  const survivingHouseholdIds = survivingHouseholds
    .map((m) => m.householdId)
    .filter((id) => !plan.householdsToDelete.includes(id));

  if (survivingHouseholdIds.length > 0) {
    const receiptAnon = await db
      .update(schema.receipts)
      .set({ imageUrl: null })
      .where(inArray(schema.receipts.householdId, survivingHouseholdIds))
      .returning({ id: schema.receipts.id });
    if (receiptAnon.length) {
      log.push({ table: getTableName(schema.receipts), action: "anonymize", count: receiptAnon.length });
      await db
        .update(schema.receiptLines)
        .set({ rawText: "" })
        .where(inArray(schema.receiptLines.receiptId, receiptAnon.map((r) => r.id)));
    }

    // Anonymize cooking sessions started by user in surviving households.
    const cookingAnon = await db
      .update(schema.cookingSessions)
      .set({ startedByUserId: null })
      .where(eq(schema.cookingSessions.startedByUserId, userId))
      .returning({ id: schema.cookingSessions.id });
    if (cookingAnon.length) log.push({ table: getTableName(schema.cookingSessions), action: "anonymize", count: cookingAnon.length });

    // Anonymize household-level records that still reference the user.
    const conflictAnon = await db
      .update(schema.inventoryConflicts)
      .set({ resolvedByUserId: null })
      .where(eq(schema.inventoryConflicts.resolvedByUserId, userId))
      .returning({ id: schema.inventoryConflicts.id });
    if (conflictAnon.length) log.push({ table: getTableName(schema.inventoryConflicts), action: "anonymize", count: conflictAnon.length });

    const transactionAnon = await db
      .update(schema.inventoryTransactions)
      .set({ actorUserId: null })
      .where(eq(schema.inventoryTransactions.actorUserId, userId))
      .returning({ id: schema.inventoryTransactions.id });
    if (transactionAnon.length) log.push({ table: getTableName(schema.inventoryTransactions), action: "anonymize", count: transactionAnon.length });

    const mealBoxAnon = await db
      .update(schema.mealBoxes)
      .set({ reservedForUserId: null })
      .where(eq(schema.mealBoxes.reservedForUserId, userId))
      .returning({ id: schema.mealBoxes.id });
    if (mealBoxAnon.length) log.push({ table: getTableName(schema.mealBoxes), action: "anonymize", count: mealBoxAnon.length });

    const shoppingAnon = await db
      .update(schema.shoppingListItems)
      .set({ addedByUserId: null })
      .where(eq(schema.shoppingListItems.addedByUserId, userId))
      .returning({ id: schema.shoppingListItems.id });
    if (shoppingAnon.length) log.push({ table: getTableName(schema.shoppingListItems), action: "anonymize", count: shoppingAnon.length });
  }

  // ---- 9. Analytics ----
  const analyticsDel = await deleteUserAnalyticsEvents(db, userId);
  if (analyticsDel > 0) log.push({ table: "analytics_events", action: "delete", count: analyticsDel });

  // ---- 10. Health/preferences/locale (delete) ----
  await db.delete(schema.userHealthProfiles).where(eq(schema.userHealthProfiles.userId, userId));
  await db.delete(schema.userPreferences).where(eq(schema.userPreferences.userId, userId));
  await db.delete(schema.userLocalePreferences).where(eq(schema.userLocalePreferences.userId, userId));

  return log;
}
