import type { Kysely } from "kysely";
import { logError } from "@/lib/logger";
import type { DB } from "../datastore/db";
import { EDMRepository } from "./edm.repository";
import { EDMService } from "@/v1/services/admin/edm/edm.service";
import { resolveEDMAssets } from "./edm-assets.services";
import { readEDMBundleEntries, type EDMBundleEntry } from "./edm-import";
import { extractSubject } from "./edm-subject";

/**
 * Import a whole archive of EDMs in one action, mirroring its folder layout.
 *
 *   campaign/welcome/index.html    → folder "campaign" › "welcome", template "welcome"
 *   campaign/reminder/index.html   → folder "campaign" › "reminder", template "reminder"
 *
 * Reviewing twenty templates one at a time in a modal is not a workflow, so
 * unlike the single-file import this one commits directly. What keeps that safe
 * is the publish rule below.
 */

export type BatchImportEntryResult = {
  htmlPath: string;
  name: string;
  folderPath: string[];
  status: "created" | "failed";
  templateId?: string;
  /** Created but deliberately left unpublished — see the publish rule. */
  needsAttention?: boolean;
  unresolved?: string[];
  message?: string;
};

export type BatchImportResult = {
  created: number;
  failed: number;
  needsAttention: number;
  foldersCreated: number;
  entries: BatchImportEntryResult[];
  warnings: string[];
};

/**
 * Find-or-create each segment of a folder path, returning the deepest folder's
 * id. Memoised across the whole import so a twenty-template archive under one
 * parent creates that parent once rather than twenty times.
 */
const ensureFolderPath = async (
  db: Kysely<DB>,
  segments: string[],
  rootParentId: string | null,
  cache: Map<string, string>,
  createdBy: string | null,
  counters: { foldersCreated: number },
): Promise<string | null> => {
  let parentId = rootParentId;
  let cacheKey = rootParentId ?? "";

  for (const rawSegment of segments) {
    const name = rawSegment.replace(/[-_]+/g, " ").trim() || rawSegment;
    cacheKey = `${cacheKey}/${name.toLowerCase()}`;

    const cached = cache.get(cacheKey);
    if (cached) {
      parentId = cached;
      continue;
    }

    // Match case-insensitively against siblings only — two folders named
    // "images" under different parents are different folders.
    const existing = (await EDMRepository.ListFolders(db)).find(
      (f) =>
        f.name.toLowerCase() === name.toLowerCase() &&
        (f.parent_id ?? null) === parentId,
    );

    if (existing) {
      cache.set(cacheKey, existing.id);
      parentId = existing.id;
      continue;
    }

    const created = await EDMRepository.CreateFolder(db, {
      name,
      parentId,
      createdBy,
    });
    counters.foldersCreated++;
    cache.set(cacheKey, created.id);
    parentId = created.id;
  }

  return parentId;
};

export const batchImportEDMBundle = async (
  db: Kysely<DB>,
  buffer: Buffer,
  opts: {
    parentFolderId?: string | null;
    createdBy?: string | null;
    /** Wrap everything in a folder named after the uploaded zip. */
    rootFolderName?: string | null;
  } = {},
): Promise<
  { ok: true; result: BatchImportResult } | { ok: false; errors: string[] }
> => {
  const parsed = await readEDMBundleEntries(buffer);
  if (!parsed.ok) return { ok: false, errors: parsed.errors };

  const createdBy = opts.createdBy ?? null;
  const counters = { foldersCreated: 0 };
  const folderCache = new Map<string, string>();

  let rootParentId = opts.parentFolderId ?? null;
  if (opts.rootFolderName?.trim()) {
    rootParentId = await ensureFolderPath(
      db,
      [opts.rootFolderName.trim()],
      opts.parentFolderId ?? null,
      folderCache,
      createdBy,
      counters,
    );
  }

  const entries: BatchImportEntryResult[] = [];

  // Sequential rather than parallel: each entry uploads several images to GCS
  // and inserts rows, and folder creation is find-or-create, which races badly
  // when two entries share an ancestor that does not exist yet.
  for (const entry of parsed.entries) {
    const outcome = await importOneEntry(
      db,
      entry,
      rootParentId,
      folderCache,
      createdBy,
      counters,
    );
    entries.push(outcome);
  }

  return {
    ok: true,
    result: {
      created: entries.filter((e) => e.status === "created").length,
      failed: entries.filter((e) => e.status === "failed").length,
      needsAttention: entries.filter((e) => e.needsAttention).length,
      foldersCreated: counters.foldersCreated,
      entries,
      warnings: parsed.warnings,
    },
  };
};

async function importOneEntry(
  db: Kysely<DB>,
  entry: EDMBundleEntry,
  rootParentId: string | null,
  folderCache: Map<string, string>,
  createdBy: string | null,
  counters: { foldersCreated: number },
): Promise<BatchImportEntryResult> {
  const base: Pick<BatchImportEntryResult, "htmlPath" | "name" | "folderPath"> = {
    htmlPath: entry.htmlPath,
    name: entry.suggestedName,
    folderPath: entry.folderPath,
  };

  try {
    const folderId = await ensureFolderPath(
      db,
      entry.folderPath,
      rootParentId,
      folderCache,
      createdBy,
      counters,
    );

    // baseDir is what makes two EDMs that both say "images/hero.png" resolve to
    // their own file rather than to whichever was indexed last.
    const resolved = await resolveEDMAssets(entry.html, entry.images, {
      baseDir: entry.dir,
    });

    const needsAttention = resolved.unresolved.length > 0;

    const created = await EDMService.getInstance().createTemplate(
      db,
      {
        name: entry.suggestedName,
        subject: extractSubject(entry.html) || entry.suggestedName,
        html_content: resolved.html,
        folder_id: folderId,
        // The publish rule: an entry with a broken image reference is created
        // but NOT published. An unpublished version has no active/ object in
        // GCS, so the SMTP worker cannot render it and it cannot be sent by
        // accident. It shows up in the console for someone to fix, which is the
        // whole point of importing in bulk.
        active: needsAttention ? 0 : 1,
      },
      createdBy,
    );

    if (!created.success) {
      return { ...base, status: "failed", message: created.message };
    }

    return {
      ...base,
      status: "created",
      templateId: created.data.id,
      needsAttention,
      unresolved: needsAttention ? resolved.unresolved : undefined,
    };
  } catch (err) {
    logError(err, `[EDM] Batch import failed for ${entry.htmlPath}`);
    return { ...base, status: "failed", message: (err as Error).message };
  }
}
