// src/lib/sites/assert-site.ts — garde tenancy : le site doit appartenir à l'org active.
import "server-only";
import { and, eq } from "drizzle-orm";
import { cache } from "react";
import { getActiveOrgId } from "@/lib/active-org";
import { db } from "@/lib/db/client";
import { contentItems, keywords, sites } from "@/lib/db/domain-schema";
import { currentUserCanAccessSite } from "./access";

/** Requête nue, testable hors session. */
export async function siteBelongsToOrg(
  siteId: string,
  orgId: string,
): Promise<boolean> {
  const [row] = await db
    .select({ id: sites.id })
    .from(sites)
    .where(and(eq(sites.id, siteId), eq(sites.orgId, orgId)));
  return row != null;
}

/**
 * Fail-closed : jette si le site n'appartient pas à l'org active de la session.
 * Dédupliqué par requête (React cache) — plusieurs actions/gardes dans le même
 * render ne refont pas la requête.
 */
export const assertSiteInActiveOrg = cache(
  async (siteId: string): Promise<void> => {
    const orgId = await getActiveOrgId();
    if (!(await siteBelongsToOrg(siteId, orgId))) {
      throw new Error("Site does not belong to the active organization");
    }
    // Members may only touch sites shared with them (owner/admin bypass).
    if (!(await currentUserCanAccessSite(siteId))) {
      throw new Error("Site is not shared with you");
    }
  },
);

/**
 * Fail-closed : résout le site du mot-clé puis vérifie qu'il appartient à
 * l'org active. Jette si le mot-clé est introuvable ou hors org.
 * Retourne le siteId résolu. Dédupliqué par requête (React cache).
 */
export const assertKeywordInActiveOrg = cache(
  async (keywordId: string): Promise<string> => {
    const [row] = await db
      .select({ siteId: keywords.siteId })
      .from(keywords)
      .where(eq(keywords.id, keywordId));
    if (!row) {
      throw new Error("Keyword does not belong to the active organization");
    }
    await assertSiteInActiveOrg(row.siteId);
    return row.siteId;
  },
);

/**
 * Fail-closed : résout le site de l'item de contenu puis vérifie qu'il
 * appartient à l'org active. Jette si l'item est introuvable ou hors org.
 * Retourne le siteId résolu. Dédupliqué par requête (React cache).
 */
export const assertContentItemInActiveOrg = cache(
  async (contentItemId: string): Promise<string> => {
    const [row] = await db
      .select({ siteId: contentItems.siteId })
      .from(contentItems)
      .where(eq(contentItems.id, contentItemId));
    if (!row) {
      throw new Error(
        "Content item does not belong to the active organization",
      );
    }
    await assertSiteInActiveOrg(row.siteId);
    return row.siteId;
  },
);
