// src/lib/content/import-actions.ts — preview/commit de l'import CSV des content_items (ELEVA-59).
"use server";
import { randomUUID } from "node:crypto";
import { and, eq, ne } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { db } from "@/lib/db/client";
import type { ContentStatus } from "@/lib/db/domain-schema";
import {
  contentItems,
  contentKeywords,
  keywords,
  sites,
} from "@/lib/db/domain-schema";
import { assertSiteInActiveOrg } from "@/lib/sites/assert-site";
import { type ParsedContentRow, parseContentRows, parseCsv } from "./csv";

export type ContentImportPreviewRow = {
  line: number;
  title: string;
  action: "new" | "update";
  status: ContentStatus;
  primaryKeywordTerm: string | null;
  primaryKeywordFound: boolean;
};

export type ContentImportPreview = {
  rows: ContentImportPreviewRow[];
  errors: { line: number; error: string }[];
  counts: { created: number; updated: number; errors: number };
};

export type ContentImportResult = {
  created: number;
  updated: number;
  errors: number;
  keywordsLinked: number;
  keywordsUnknown: number;
};

/** Items existants du site indexés par `lower(trim(title))`. */
async function loadItemIdsByTitle(
  siteId: string,
): Promise<Map<string, string>> {
  const items = await db
    .select({ id: contentItems.id, title: contentItems.title })
    .from(contentItems)
    .where(eq(contentItems.siteId, siteId));
  return new Map(items.map((i) => [i.title.trim().toLowerCase(), i.id]));
}

/** Keywords du site indexés par `lower(term)`. */
async function loadKeywordIdsByTerm(
  siteId: string,
): Promise<Map<string, string>> {
  const kws = await db
    .select({ id: keywords.id, term: keywords.term })
    .from(keywords)
    .where(eq(keywords.siteId, siteId));
  return new Map(kws.map((k) => [k.term.toLowerCase(), k.id]));
}

/** Réassocie chaque row parsée à sa ligne CSV (1-indexée, en-tête = ligne 1) :
 *  rows et errors préservent l'ordre des lignes, on saute les lignes en erreur. */
function rowsWithLines(
  rows: ParsedContentRow[],
  errors: { line: number; error: string }[],
): { row: ParsedContentRow; line: number }[] {
  const errorLines = new Set(errors.map((e) => e.line));
  const out: { row: ParsedContentRow; line: number }[] = [];
  let i = 0;
  for (let line = 2; line <= rows.length + errors.length + 1; line++) {
    if (errorLines.has(line)) continue;
    out.push({ row: rows[i++], line });
  }
  return out;
}

export async function previewContentImportAction(
  siteId: string,
  csvText: string,
): Promise<ContentImportPreview> {
  await assertSiteInActiveOrg(siteId);
  const { rows, errors } = parseContentRows(parseCsv(csvText));
  const itemsByTitle = await loadItemIdsByTitle(siteId);
  const kwByTerm = await loadKeywordIdsByTerm(siteId);
  const previewRows: ContentImportPreviewRow[] = rowsWithLines(
    rows,
    errors,
  ).map(({ row, line }) => ({
    line,
    title: row.title,
    action: itemsByTitle.has(row.title.trim().toLowerCase()) ? "update" : "new",
    status: row.status,
    primaryKeywordTerm: row.primaryKeywordTerm,
    primaryKeywordFound: row.primaryKeywordTerm
      ? kwByTerm.has(row.primaryKeywordTerm.toLowerCase())
      : true,
  }));
  return {
    rows: previewRows,
    errors,
    counts: {
      created: previewRows.filter((r) => r.action === "new").length,
      updated: previewRows.filter((r) => r.action === "update").length,
      errors: errors.length,
    },
  };
}

export async function commitContentImportAction(
  siteId: string,
  csvText: string,
): Promise<ContentImportResult> {
  await assertSiteInActiveOrg(siteId);
  const { rows, errors } = parseContentRows(parseCsv(csvText));
  const [site] = await db
    .select({
      primaryLanguage: sites.primaryLanguage,
      targetCountries: sites.targetCountries,
    })
    .from(sites)
    .where(eq(sites.id, siteId))
    .limit(1);
  if (!site) throw new Error("Site not found");
  const defaultLang = site.primaryLanguage;
  const defaultCountry = site.targetCountries[0] ?? "FR";
  const itemsByTitle = await loadItemIdsByTitle(siteId);
  const kwByTerm = await loadKeywordIdsByTerm(siteId);

  let created = 0;
  let updated = 0;
  let keywordsLinked = 0;
  let keywordsUnknown = 0;

  await db.transaction(async (tx) => {
    for (const row of rows) {
      const titleKey = row.title.trim().toLowerCase();
      let itemId = itemsByTitle.get(titleKey);
      if (itemId) {
        // update partiel : champs fournis non-null + status toujours.
        await tx
          .update(contentItems)
          .set({
            status: row.status,
            ...(row.assignee !== null ? { assignee: row.assignee } : {}),
            ...(row.dueDate !== null ? { dueDate: new Date(row.dueDate) } : {}),
            ...(row.url !== null ? { url: row.url } : {}),
            ...(row.lang !== null ? { lang: row.lang } : {}),
            ...(row.country !== null ? { country: row.country } : {}),
          })
          .where(eq(contentItems.id, itemId));
        updated++;
      } else {
        itemId = randomUUID();
        await tx.insert(contentItems).values({
          id: itemId,
          siteId,
          title: row.title,
          status: row.status,
          lang: row.lang ?? defaultLang,
          country: row.country ?? defaultCountry,
          assignee: row.assignee,
          dueDate: row.dueDate ? new Date(row.dueDate) : null,
          url: row.url,
        });
        itemsByTitle.set(titleKey, itemId); // un doublon plus bas devient un update
        created++;
      }

      // Mot-clé principal par terme : sync primaryKeywordId + content_keywords
      // role primary (l'ancien primary éventuel est rétrogradé en secondary).
      if (row.primaryKeywordTerm) {
        const kwId = kwByTerm.get(row.primaryKeywordTerm.toLowerCase());
        if (kwId) {
          await tx
            .update(contentItems)
            .set({ primaryKeywordId: kwId })
            .where(eq(contentItems.id, itemId));
          await tx
            .update(contentKeywords)
            .set({ role: "secondary" })
            .where(
              and(
                eq(contentKeywords.contentItemId, itemId),
                eq(contentKeywords.role, "primary"),
                ne(contentKeywords.keywordId, kwId),
              ),
            );
          await tx
            .insert(contentKeywords)
            .values({
              contentItemId: itemId,
              keywordId: kwId,
              siteId,
              role: "primary",
            })
            .onConflictDoUpdate({
              target: [
                contentKeywords.contentItemId,
                contentKeywords.keywordId,
              ],
              set: { role: "primary" },
            });
          keywordsLinked++;
        } else {
          keywordsUnknown++;
        }
      }

      // Secondaires : onConflictDoNothing (un terme déjà primary le reste).
      for (const term of row.secondaryKeywordTerms) {
        const kwId = kwByTerm.get(term.toLowerCase());
        if (!kwId) {
          keywordsUnknown++;
          continue;
        }
        await tx
          .insert(contentKeywords)
          .values({
            contentItemId: itemId,
            keywordId: kwId,
            siteId,
            role: "secondary",
          })
          .onConflictDoNothing();
        keywordsLinked++;
      }
    }
  });

  revalidatePath("/content/planning");
  return {
    created,
    updated,
    errors: errors.length,
    keywordsLinked,
    keywordsUnknown,
  };
}
