// src/lib/spec/audit/actions.ts — server actions for the per-site spec audit.
// Tenancy is enforced via assertSiteInActiveOrg on every entry point.
"use server";
import { desc, eq, inArray } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { getActiveOrgId } from "@/lib/active-org";
import { db } from "@/lib/db/client";
import { siteAudits } from "@/lib/db/domain-schema";
import { assertSiteInActiveOrg } from "@/lib/sites/assert-site";
import { getProjectsForOrg, getSiteById } from "@/lib/sites/queries";
import { persistSiteAudit } from "./persist";
import type { AuditResult } from "./run";
import { runSiteAudit } from "./run";
import { fetchSiteSnapshot } from "./snapshot";

/**
 * Audit the active site against the fetchable spec checks and persist a new
 * `site_audits` row. Throws if the site has no URL configured.
 */
export async function runSiteAuditAction(siteId: string): Promise<AuditResult> {
  await assertSiteInActiveOrg(siteId);
  const site = await getSiteById(siteId, await getActiveOrgId());
  if (!site?.url) {
    throw new Error("Le site n'a pas d'URL configurée.");
  }

  const snapshot = await fetchSiteSnapshot(site.url);
  const result = runSiteAudit(snapshot, {
    targetCountries: site.targetCountries ?? [],
  });

  await persistSiteAudit(siteId, result);

  revalidatePath("/flight-checker");
  return result;
}

/** The latest audit for a site (org-scoped), or null when none has run yet. */
export async function getLatestAuditAction(
  siteId: string,
): Promise<AuditResult | null> {
  await assertSiteInActiveOrg(siteId);
  const [row] = await db
    .select()
    .from(siteAudits)
    .where(eq(siteAudits.siteId, siteId))
    .orderBy(desc(siteAudits.ranAt))
    .limit(1);
  if (!row) return null;
  return {
    ranAt: row.ranAt.toISOString(),
    score: row.score,
    results: row.results,
  };
}

/** One row per site in the active org with its latest audit summary. */
export type OrgAuditOverviewRow = {
  siteId: string;
  name: string;
  url: string | null;
  score: number | null;
  ranAt: string | null;
  pass: number;
  fail: number;
  warn: number;
};

/**
 * Cross-site overview: for every site of the active org, the latest audit's
 * score, run date, and pass/fail/warn counts. Sites never audited → null score
 * and zeroed counts. Org tenancy is implicit (sites scoped by active org).
 *
 * One query for the audits (scalar columns only — `results` jsonb is never
 * loaded here), instead of one `select()` per site: site_audits has a unique
 * index on site_id, so this is a single index-only-ish lookup over the org's
 * site ids rather than N round-trips + N TOASTed jsonb payloads.
 */
export async function getOrgAuditOverviewAction(): Promise<
  OrgAuditOverviewRow[]
> {
  const projects = await getProjectsForOrg(await getActiveOrgId());
  if (projects.length === 0) return [];

  const rows = await db
    .select({
      siteId: siteAudits.siteId,
      score: siteAudits.score,
      ranAt: siteAudits.ranAt,
      passCount: siteAudits.passCount,
      failCount: siteAudits.failCount,
      warnCount: siteAudits.warnCount,
    })
    .from(siteAudits)
    .where(
      inArray(
        siteAudits.siteId,
        projects.map((site) => site.id),
      ),
    );
  const bySiteId = new Map(rows.map((row) => [row.siteId, row]));

  return projects.map((site): OrgAuditOverviewRow => {
    const row = bySiteId.get(site.id);
    return {
      siteId: site.id,
      name: site.name,
      url: site.url || null,
      score: row?.score ?? null,
      ranAt: row?.ranAt.toISOString() ?? null,
      pass: row?.passCount ?? 0,
      fail: row?.failCount ?? 0,
      warn: row?.warnCount ?? 0,
    };
  });
}
