"use server";

import { z } from "zod";
import { clientKey, rateLimit } from "@/lib/rate-limit";

/**
 * Set to the identifier of the ESP/CRM the day one is wired. As long as it is
 * absent, the action answers `unavailable` instead of pretending to store the
 * address — the audit's §9 dishonest "Thank you for subscribing".
 */
const NEWSLETTER_PROVIDER = process.env.NEWSLETTER_PROVIDER?.trim();

/** 5 submissions per client per 10 minutes. */
const RATE_LIMIT = 5;
const RATE_WINDOW_MS = 10 * 60 * 1000;

/**
 * `.trim().toLowerCase()` has to run BEFORE the format check: Zod applies the
 * transforms and the checks in the order they are chained, so `z.email().trim()`
 * would reject " a@b.com " before ever trimming it.
 *
 * `max(254)` is the RFC 5321 ceiling for an address — it also caps an
 * unbounded payload on a public endpoint.
 */
const newsletterSchema = z.object({
  email: z
    .string({ error: "invalid" })
    .trim()
    .toLowerCase()
    .pipe(z.email({ error: "invalid" }).max(254, { error: "invalid" })),
  /** Honeypot: a human never sees this field, so anything but "" is a bot. */
  website: z.literal("", { error: "invalid" }),
  /** Native checkboxes only appear in the payload when they are checked. */
  consent: z.literal("on", { error: "consent" }),
});

type NewsletterError = "invalid" | "consent" | "rateLimited";

export type NewsletterState = {
  /**
   * `unavailable` is the honest outcome: no ESP or CRM is wired yet, so the
   * address is not stored anywhere and the UI must not claim a subscription.
   */
  status: "idle" | "unavailable" | "error";
  /** Message code; the client maps it onto its own localized labels. */
  error?: NewsletterError;
};

export async function subscribeNewsletter(
  _prev: NewsletterState,
  formData: FormData,
): Promise<NewsletterState> {
  // A Server Action is a public POST endpoint: throttle before doing any work.
  if (!rateLimit(`newsletter:${await clientKey()}`, RATE_LIMIT, RATE_WINDOW_MS)) {
    return { status: "error", error: "rateLimited" };
  }

  const parsed = newsletterSchema.safeParse({
    email: formData.get("email"),
    website: formData.get("website") ?? "",
    consent: formData.get("consent"),
  });

  if (!parsed.success) {
    const code = parsed.error.issues[0]?.message;
    return { status: "error", error: code === "consent" ? "consent" : "invalid" };
  }

  if (NEWSLETTER_PROVIDER) {
    // The variable is set but no integration ships yet: make the dropped
    // subscription visible in the logs instead of losing it silently. Only the
    // provider name is logged — never the address.
    console.warn(
      `[newsletter] NEWSLETTER_PROVIDER="${NEWSLETTER_PROVIDER}" is configured but no integration is wired yet.`,
    );
  }

  // TODO(provider): hand `parsed.data.email` to the ESP/CRM and return a real
  // success state. Until then the address is validated, then dropped — and the
  // form says exactly that instead of thanking the visitor for nothing.
  return { status: "unavailable" };
}
