// src/lib/backlink/planning-actions.ts — actions du board netlinking (scopées site).
"use server";
import { and, eq, isNull, sql } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { db } from "@/lib/db/client";
import {
  type BacklinkStatus,
  backlinkOutreach,
  backlinks,
  linkSources,
} from "@/lib/db/domain-schema";
import { assertSiteInActiveOrg } from "@/lib/sites/assert-site";

const PLANNING_PATH = "/backlink/planning";

/** Avance un backlink ; passage à live pose firstSeen (sans écraser). Scopé site. */
export async function moveBacklinkAction(
  siteId: string,
  backlinkId: string,
  status: BacklinkStatus,
): Promise<{ ok: boolean }> {
  await assertSiteInActiveOrg(siteId);
  const updated = await db
    .update(backlinks)
    .set({ status })
    .where(and(eq(backlinks.id, backlinkId), eq(backlinks.siteId, siteId)))
    .returning({ id: backlinks.id });
  if (updated.length === 0) return { ok: false };
  if (status === "live") {
    await db
      .update(backlinks)
      .set({ firstSeen: sql`now()` })
      .where(and(eq(backlinks.id, backlinkId), isNull(backlinks.firstSeen)));
  }
  revalidatePath(PLANNING_PATH);
  return { ok: true };
}

/** Le backlink appartient-il au site ? (garde des actions) */
async function belongsToSite(
  backlinkId: string,
  siteId: string,
): Promise<boolean> {
  const row = await db
    .select({ id: backlinks.id })
    .from(backlinks)
    .where(and(eq(backlinks.id, backlinkId), eq(backlinks.siteId, siteId)));
  return row.length > 0;
}

type OutreachPatch = {
  contact?: string | null;
  channel?: string | null;
  attempts?: number;
  lastContactAt?: string | null;
  notes?: string | null;
};

/** Upsert PARTIEL : seules les clés présentes dans `patch` sont écrites au conflit. */
export async function upsertOutreachAction(
  siteId: string,
  backlinkId: string,
  patch: OutreachPatch,
): Promise<{ ok: boolean }> {
  await assertSiteInActiveOrg(siteId);
  if (!(await belongsToSite(backlinkId, siteId))) return { ok: false };
  const values: typeof backlinkOutreach.$inferInsert = {
    backlinkId,
    contact: patch.contact ?? null,
    channel: patch.channel ?? null,
    attempts: patch.attempts ?? 0,
    lastContactAt: patch.lastContactAt ? new Date(patch.lastContactAt) : null,
    notes: patch.notes ?? null,
  };
  const set: Partial<typeof backlinkOutreach.$inferInsert> = {
    updatedAt: sql`now()` as unknown as Date,
  };
  if ("contact" in patch) set.contact = values.contact;
  if ("channel" in patch) set.channel = values.channel;
  if ("attempts" in patch) set.attempts = values.attempts;
  if ("lastContactAt" in patch) set.lastContactAt = values.lastContactAt;
  if ("notes" in patch) set.notes = values.notes;
  await db
    .insert(backlinkOutreach)
    .values(values)
    .onConflictDoUpdate({ target: backlinkOutreach.backlinkId, set });
  revalidatePath(PLANNING_PATH);
  return { ok: true };
}

/** Flags toxic/disavow (patch partiel). Scopé site. */
export async function setBacklinkFlagsAction(
  siteId: string,
  backlinkId: string,
  flags: { toxic?: boolean; disavow?: boolean },
): Promise<{ ok: boolean }> {
  await assertSiteInActiveOrg(siteId);
  const set: Partial<typeof backlinks.$inferInsert> = {};
  if ("toxic" in flags) set.toxic = flags.toxic;
  if ("disavow" in flags) set.disavow = flags.disavow;
  if (Object.keys(set).length === 0) return { ok: true };
  const updated = await db
    .update(backlinks)
    .set(set)
    .where(and(eq(backlinks.id, backlinkId), eq(backlinks.siteId, siteId)))
    .returning({ id: backlinks.id });
  revalidatePath(PLANNING_PATH);
  return { ok: updated.length > 0 };
}

/** Domaines flagués disavow du site (le client génère le fichier). */
export async function fetchDisavowDomainsAction(
  siteId: string,
): Promise<string[]> {
  await assertSiteInActiveOrg(siteId);
  const rows = await db
    .select({ domainNorm: linkSources.domainNorm })
    .from(backlinks)
    .innerJoin(linkSources, eq(linkSources.id, backlinks.linkSourceId))
    .where(and(eq(backlinks.siteId, siteId), eq(backlinks.disavow, true)));
  return [...new Set(rows.map((r) => r.domainNorm))].sort();
}
