import { cookies } from "next/headers";
import { logger } from "../logger";
import { prisma } from "./db";
import { getSessionPepper } from "./env";
import { hashRuleClaimToken } from "./hash";

const GUEST_RULE_CLAIM_COOKIE_NAME = "cr_rule_claim";

const CLAIM_COOKIE_MAX_AGE_SEC = 60 * 60 * 24 * 30;
const CLAIM_COOKIE_MAX_TOKENS = 8;
const TOKEN_MIN_LEN = 16;
const TOKEN_MAX_LEN = 128;

function cookieOptions(): {
  httpOnly: true;
  secure: boolean;
  sameSite: "lax";
  path: "/";
  maxAge: number;
} {
  return {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "lax",
    path: "/",
    maxAge: CLAIM_COOKIE_MAX_AGE_SEC,
  };
}

function parseClaimTokens(raw: string | undefined): string[] {
  if (!raw) return [];
  try {
    const parsed: unknown = JSON.parse(raw);
    if (!Array.isArray(parsed)) return [];
    const tokens: string[] = [];
    for (const item of parsed) {
      if (typeof item !== "string") continue;
      if (item.length < TOKEN_MIN_LEN || item.length > TOKEN_MAX_LEN) continue;
      tokens.push(item);
    }
    return tokens.slice(-CLAIM_COOKIE_MAX_TOKENS);
  } catch {
    return [];
  }
}

/** Remember a guest-publish claim secret on this browser (httpOnly). */
export async function issueGuestRuleClaimCookie(token: string): Promise<void> {
  const store = await cookies();
  const existing = parseClaimTokens(
    store.get(GUEST_RULE_CLAIM_COOKIE_NAME)?.value,
  );
  const next = [
    ...existing.filter((item) => item !== token),
    token,
  ].slice(-CLAIM_COOKIE_MAX_TOKENS);
  store.set(
    GUEST_RULE_CLAIM_COOKIE_NAME,
    JSON.stringify(next),
    cookieOptions(),
  );
}

async function clearGuestRuleClaimCookie(): Promise<void> {
  const store = await cookies();
  store.set(GUEST_RULE_CLAIM_COOKIE_NAME, "", {
    ...cookieOptions(),
    maxAge: 0,
  });
}

/**
 * Attach ownerless published rules whose claim secrets are in this browser's
 * cookie to `userId`, then drop the cookie. Never throws to the caller.
 */
export async function claimGuestRulesForUser(userId: string): Promise<number> {
  if (typeof userId !== "string" || userId.trim() === "") return 0;

  try {
    const store = await cookies();
    const tokens = parseClaimTokens(
      store.get(GUEST_RULE_CLAIM_COOKIE_NAME)?.value,
    );
    if (tokens.length === 0) return 0;

    let pepper: string;
    try {
      pepper = getSessionPepper();
    } catch {
      return 0;
    }

    let claimed = 0;
    for (const token of tokens) {
      const claimTokenHash = hashRuleClaimToken(token, pepper);
      const result = await prisma.publishedRule.updateMany({
        where: { claimTokenHash, userId: null },
        data: { userId, claimTokenHash: null },
      });
      claimed += result.count;
    }

    await clearGuestRuleClaimCookie();
    return claimed;
  } catch (err) {
    logger.warn("[guest-rule-claim] failed", err);
    return 0;
  }
}
