// src/lib/backlink/actions.ts — server actions: target reservoir sources for a site.
"use server";
import { randomUUID } from "node:crypto";
import { and, eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { db } from "@/lib/db/client";
import { backlinks } from "@/lib/db/domain-schema";
import { assertSiteInActiveOrg } from "@/lib/sites/assert-site";
import { filterLinkSourceIdsForActiveOrg } from "./assert-link-source";

const RESERVOIR_PATH = "/backlink/reservoir";

/** Mark sources as prospects for a site (idempotent). */
export async function addSourcesToSiteAction(
  sourceIds: string[],
  siteId: string,
): Promise<void> {
  await assertSiteInActiveOrg(siteId);
  if (sourceIds.length === 0) return;
  // Tenancy (audit sécu B1) — un linkSourceId d'une autre org est silencieusement
  // écarté avant tout insert (fail-closed bulk, même sémantique que les mots-clés).
  const validSourceIds = await filterLinkSourceIdsForActiveOrg(sourceIds);
  if (validSourceIds.length === 0) return;
  // Dédup par la contrainte unique (link_source_id, site_id) — résiste aux
  // appels concurrents, contrairement à l'ancien select-puis-insert.
  await db
    .insert(backlinks)
    .values(
      validSourceIds.map((linkSourceId) => ({
        id: randomUUID(),
        linkSourceId,
        siteId,
        status: "prospect" as const,
      })),
    )
    .onConflictDoNothing();
  revalidatePath(RESERVOIR_PATH);
}

/** Remove a source's targeting for a site. */
export async function removeSourceFromSiteAction(
  sourceId: string,
  siteId: string,
): Promise<void> {
  await assertSiteInActiveOrg(siteId);
  await db
    .delete(backlinks)
    .where(
      and(eq(backlinks.linkSourceId, sourceId), eq(backlinks.siteId, siteId)),
    );
  revalidatePath(RESERVOIR_PATH);
}
