"use server";

import { z } from "zod";

import { getT } from "@/lib/content";
import type { FormState } from "@/lib/form-state";
import { isLocale, LOCALES, type Locale } from "@/lib/locales";
import { MODULE_IDS, SECTOR_IDS, type ModuleId, type SectorId } from "@/lib/routes";
import { IS_PRODUCTION } from "@/lib/site";

/**
 * RRYM / 002 — SULSYS — the two forms.
 *
 * Production Blueprint v1, section 12: fields validated with Zod on the
 * server, a honeypot field and a minimum fill time, no client-side fetch, and
 * no data stored anywhere except the email — which is what the privacy notice
 * says.
 *
 * DELIVERY SAFETY (this build is a preview):
 *   `CONTACT_TO` is never defaulted. With no recipient configured — the state
 *   of every preview and every local run — the submission is written to the
 *   server log and nothing is sent, so info@sulsys.net cannot be contacted
 *   from a preview by accident. Mail starts flowing only when someone sets
 *   RESEND_API_KEY and CONTACT_TO deliberately (Blueprint 14).
 */

const MIN_FILL_MS = 3000;

/** The sector choices offered by the demo form. */
const DEMO_SECTORS = ["other", ...SECTOR_IDS] as readonly ["other", ...(SectorId | "other")[]];

/* -------------------------------------------------------------------------- */
/* Validation                                                                 */
/* -------------------------------------------------------------------------- */

/**
 * Phone numbers are typed in many shapes locally (075 251 235,
 * +389 75 251 235, 075-251-235), so the check is deliberately loose: at least
 * six digits once separators are removed.
 */
const phone = z
  .string()
  .trim()
  .min(1)
  .refine((value) => value.replace(/[^\d]/g, "").length >= 6, "phone");

function baseSchema(locale: Locale) {
  return z.object({
    locale: z.literal(locale),
    name: z.string().trim().min(2).max(120),
    company: z.string().trim().max(160).optional().default(""),
    phone,
    // Honeypot: a real visitor never fills this.
    website: z.string().max(0),
    startedAt: z.coerce.number().int().nonnegative(),
  });
}

function demoSchema(locale: Locale) {
  return baseSchema(locale).extend({
    // "other" is offered because Sulsys's own list runs to nineteen business
    // types; the four cluster pages cannot cover every caller.
    sector: z.enum(DEMO_SECTORS),
    preferredLanguage: z.enum(LOCALES),
    message: z.string().trim().max(2000).optional().default(""),
  });
}

function supportSchema(locale: Locale) {
  return baseSchema(locale).extend({
    module: z.enum(MODULE_IDS as readonly [ModuleId, ...ModuleId[]]),
    description: z.string().trim().min(10).max(2000),
  });
}

/* -------------------------------------------------------------------------- */
/* Delivery                                                                   */
/* -------------------------------------------------------------------------- */

type Submission = {
  kind: "demo" | "support";
  locale: Locale;
  subject: string;
  fields: Record<string, string>;
  replyTo?: string;
};

async function deliver(submission: Submission): Promise<boolean> {
  const apiKey = process.env.RESEND_API_KEY;
  const to = process.env.CONTACT_TO;
  const from = process.env.CONTACT_FROM;

  const body = Object.entries(submission.fields)
    .filter(([, value]) => value.length > 0)
    .map(([key, value]) => `${key}: ${value}`)
    .join("\n");

  // Preview and local development: log, do not send.
  if (!apiKey || !to || !from) {
    console.info(
      [
        "",
        "─".repeat(72),
        `[${submission.kind}] ${submission.subject}`,
        `locale: ${submission.locale}`,
        IS_PRODUCTION
          ? "NOT SENT — RESEND_API_KEY, CONTACT_TO or CONTACT_FROM is missing in production."
          : "NOT SENT — preview/local build. Configure RESEND_API_KEY, CONTACT_TO and CONTACT_FROM to enable delivery.",
        "─".repeat(72),
        body,
        "─".repeat(72),
        "",
      ].join("\n"),
    );
    // The visitor is told the request went through, because from their side it
    // did: it is recorded. Returning an error here would train Kujto's future
    // customers to distrust the form.
    return true;
  }

  try {
    const response = await fetch("https://api.resend.com/emails", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        from,
        to: [to],
        subject: submission.subject,
        text: body,
        ...(submission.replyTo ? { reply_to: submission.replyTo } : {}),
      }),
    });

    if (!response.ok) {
      console.error(`Resend returned ${response.status}: ${await response.text()}`);
      return false;
    }
    return true;
  } catch (error) {
    console.error("Form delivery failed:", error);
    return false;
  }
}

/* -------------------------------------------------------------------------- */
/* Shared guards                                                              */
/* -------------------------------------------------------------------------- */

function readLocale(formData: FormData): Locale | null {
  const value = formData.get("locale");
  return typeof value === "string" && isLocale(value) ? value : null;
}

/** Honeypot plus fill-time. Both failures look like success to a bot. */
function looksAutomated(formData: FormData): boolean {
  const honeypot = formData.get("website");
  if (typeof honeypot === "string" && honeypot.length > 0) return true;

  const startedAt = Number(formData.get("startedAt"));
  if (!Number.isFinite(startedAt) || startedAt <= 0) return false;
  return Date.now() - startedAt < MIN_FILL_MS;
}

function fieldErrors(error: z.ZodError, locale: Locale, kind: "demo" | "support") {
  const t = getT(locale);
  const map: Record<string, string> = {};
  for (const issue of error.issues) {
    const field = String(issue.path[0] ?? "");
    switch (field) {
      case "name":
        map.name = t("form.errName");
        break;
      case "phone":
        map.phone = t("form.errPhone");
        break;
      case "sector":
        map.sector = t("form.errSector");
        break;
      case "module":
        map.module = t("form.errModule");
        break;
      case "description":
        map.description = t("form.errDescription");
        break;
      case "preferredLanguage":
        map.preferredLanguage = t("form.errLanguage");
        break;
      default:
        map[kind] = t("form.errGeneric");
        break;
    }
  }
  return map;
}

/* -------------------------------------------------------------------------- */
/* Actions                                                                    */
/* -------------------------------------------------------------------------- */

export async function submitDemoRequest(
  _previous: FormState,
  formData: FormData,
): Promise<FormState> {
  const locale = readLocale(formData);
  if (!locale) return { status: "error", message: "Invalid request." };

  const t = getT(locale);

  /*
   * Validation comes first. If the automation guard ran before it, a person
   * who filled the form in a hurry and missed a field would be told the
   * request was sent — and it would be discarded. A bot gets the same
   * validation errors, which tell it nothing.
   */
  const parsed = demoSchema(locale).safeParse(Object.fromEntries(formData));
  if (!parsed.success) {
    return {
      status: "error",
      errors: fieldErrors(parsed.error, locale, "demo"),
      message: t("form.errorTitle"),
    };
  }

  // A complete, well-formed submission that arrived impossibly fast, or that
  // filled the honeypot, is accepted to the sender and never delivered.
  if (looksAutomated(formData)) {
    return { status: "success" };
  }

  const data = parsed.data;
  const sent = await deliver({
    kind: "demo",
    locale,
    subject: `[Nexys demo] ${data.name}${data.company ? ` — ${data.company}` : ""}`,
    fields: {
      Name: data.name,
      Company: data.company,
      Sector: data.sector,
      Phone: data.phone,
      "Preferred language": data.preferredLanguage,
      Message: data.message,
      "Page language": locale,
      Received: new Date().toISOString(),
    },
  });

  return sent ? { status: "success" } : { status: "error", message: t("form.errGeneric") };
}

export async function submitSupportRequest(
  _previous: FormState,
  formData: FormData,
): Promise<FormState> {
  const locale = readLocale(formData);
  if (!locale) return { status: "error", message: "Invalid request." };

  const t = getT(locale);

  // Same order as the demo form: validate, then absorb.
  const parsed = supportSchema(locale).safeParse(Object.fromEntries(formData));
  if (!parsed.success) {
    return {
      status: "error",
      errors: fieldErrors(parsed.error, locale, "support"),
      message: t("form.errorTitle"),
    };
  }

  if (looksAutomated(formData)) {
    return { status: "success" };
  }

  const data = parsed.data;
  const sent = await deliver({
    kind: "support",
    locale,
    // Blueprint 07: the support request uses the same pipeline with a prefix.
    subject: `[Nexys support] ${data.module} — ${data.name}`,
    fields: {
      Name: data.name,
      Company: data.company,
      Module: data.module,
      Phone: data.phone,
      Description: data.description,
      "Page language": locale,
      Received: new Date().toISOString(),
    },
  });

  return sent ? { status: "success" } : { status: "error", message: t("form.errGeneric") };
}

/** Exposed for the contact page copy so it can name the fallback honestly. */
export async function deliveryIsConfigured(): Promise<boolean> {
  return Boolean(process.env.RESEND_API_KEY && process.env.CONTACT_TO && process.env.CONTACT_FROM);
}
