// src/lib/seranking/actions.ts — server action du « Sync SE-Ranking » (réservoir).
"use server";
import { eq } from "drizzle-orm";
import { updateTag } from "next/cache";
import { env } from "@/env";
import {
  QuotaError,
  type QuotaExceeded,
  quotaPayload,
} from "@/lib/billing/quotas";
import { db } from "@/lib/db/client";
import { keywords, sites } from "@/lib/db/domain-schema";
import {
  type KeywordSelection,
  selectionWhere,
} from "@/lib/keywords/list-query";
import { keywordOverviewTag } from "@/lib/keywords/queries";
import { assertSiteInActiveOrg } from "@/lib/sites/assert-site";
import { getSerankingAccess } from "./balance";
import { SerankingError, serankingErrorMessage } from "./client";
import {
  estimateSync,
  MAX_SYNC,
  type SerankingSyncResult,
  syncSerankingForSelection,
} from "./sync";

export type SyncSerankingResult =
  | ({ ok: true } & SerankingSyncResult)
  | { ok: false; error: string; quota?: QuotaExceeded };

export type SerankingEstimate = {
  keywords: number;
  requests: number;
  countries: number;
  skippedCountries: string[];
  access: { mode: "unlimited" | "byok" | "metered"; balance: number };
};

/** Estime le coût (en syncs) d'une sélection — pas d'appel API, sert le
 *  dialog de confirmation. Mêmes gates que `syncSerankingAction` (env,
 *  tenancy, sélection vide) : le dialog ne doit jamais afficher une
 *  estimation sur une sélection que le sync refuserait ensuite. */
export async function estimateSerankingSyncAction(
  siteId: string,
  selection: KeywordSelection,
): Promise<({ ok: true } & SerankingEstimate) | { ok: false; error: string }> {
  if (!env.serankingEnabled) {
    return { ok: false, error: "SE-Ranking n'est pas configuré." };
  }
  await assertSiteInActiveOrg(siteId);
  if (selection.mode === "ids" && selection.ids.length === 0) {
    return { ok: false, error: "Sélection vide." };
  }
  const [site] = await db
    .select({ orgId: sites.orgId })
    .from(sites)
    .where(eq(sites.id, siteId));
  if (!site) return { ok: false, error: "Site introuvable." };

  const access = await getSerankingAccess(site.orgId);
  const rows = await db
    .select({
      id: keywords.id,
      term: keywords.term,
      country: keywords.country,
      sources: keywords.sources,
    })
    .from(keywords)
    .where(selectionWhere(siteId, selection))
    .limit(MAX_SYNC);
  const estimate = estimateSync(rows);

  return {
    ok: true,
    ...estimate,
    access: {
      mode: access.mode,
      balance: access.mode === "metered" ? access.balance : 0,
    },
  };
}

/** Enrichit les mots-clés sélectionnés via l'API bulk SE-Ranking. Même gate
 *  que les imports : tenancy + site partagé (assertSiteInActiveOrg) ; le
 *  quota keywords (kw×source) est vérifié dans sync.ts. */
export async function syncSerankingAction(
  siteId: string,
  selection: KeywordSelection,
): Promise<SyncSerankingResult> {
  if (!env.serankingEnabled) {
    return { ok: false, error: "SE-Ranking n'est pas configuré." };
  }
  await assertSiteInActiveOrg(siteId);
  if (selection.mode === "ids" && selection.ids.length === 0) {
    return { ok: false, error: "Sélection vide." };
  }
  const [site] = await db
    .select({ orgId: sites.orgId })
    .from(sites)
    .where(eq(sites.id, siteId));
  if (!site) return { ok: false, error: "Site introuvable." };

  const access = await getSerankingAccess(site.orgId);
  if (access.mode === "metered" && access.balance <= 0) {
    return {
      ok: false,
      error: "Solde de syncs épuisé — recharge depuis Facturation.",
    };
  }

  try {
    const res = await syncSerankingForSelection(
      siteId,
      site.orgId,
      selection,
      access,
    );
    updateTag(keywordOverviewTag(siteId));
    return { ok: true, ...res };
  } catch (e) {
    if (e instanceof QuotaError) {
      return { ok: false, error: e.message, quota: quotaPayload(e) };
    }
    if (e instanceof SerankingError) {
      // "no-syncs" mid-sync : e.message porte le compte partiel des chunks
      // déjà commités (« X enrichi(s) avant épuisement ») — le surfacer.
      const detail =
        e.kind === "no-syncs" && e.message ? ` (${e.message})` : "";
      return { ok: false, error: `${serankingErrorMessage(e)}${detail}` };
    }
    throw e;
  }
}
