import { registration } from "../config/registration.ts";
export const value = (form: FormData, key: string) =>
  String(form.get(key) || "").trim();
export const cents = (input: string) =>
  Math.round(Number((input || "0").replace(",", ".")) * 100);
export class ValidationError extends Error {
  code: string;
  constructor(code: string) {
    super(code);
    this.code = code;
  }
}
const fail = (code: string): never => {
  throw new ValidationError(code);
};
const email = (s: string) =>
  s.length <= 254 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
function date(s: string) {
  return (
    /^\d{4}-\d{2}-\d{2}$/.test(s) &&
    Number.isFinite(Date.parse(s)) &&
    new Date(s).toISOString().slice(0, 10) === s
  );
}
export function validSignature(type: string, b: Uint8Array) {
  if (type === "application/pdf")
    return (
      b[0] === 0x25 &&
      b[1] === 0x50 &&
      b[2] === 0x44 &&
      b[3] === 0x46 &&
      b[4] === 0x2d
    );
  if (type === "image/png")
    return [137, 80, 78, 71, 13, 10, 26, 10].every((n, i) => b[i] === n);
  if (type === "image/jpeg")
    return b[0] === 255 && b[1] === 216 && b[2] === 255;
  return false;
}
export async function validateApplication(form: FormData) {
  const fields = [
    "childName",
    "birthDate",
    "socialResponse",
    "childAddress",
    "childPostalCode",
    "childLocality",
    "childCitizenCard",
    "childCitizenCardExpiry",
    "childTaxNumber",
    "childSocialSecurity",
    "guardianMode",
    "confirmationEmail",
  ];
  if (fields.some((k) => !value(form, k))) fail("required_fields");
  if (!/^[a-zA-Z0-9_-]{16,100}$/.test(value(form, "idempotencyKey")))
    fail("invalid_fields");
  for (const [key, v] of form) {
    const limit = /name$/i.test(key)
      ? 160
      : /address/i.test(key)
        ? 300
        : /email/i.test(key)
          ? 254
          : key === "siblingNumbers"
            ? 300
            : 100;
    if (
      key.length > 100 ||
      (typeof v === "string" &&
        (v.length > limit ||
          /[\u0000-\u0008\u000b\u000c\u000e-\u001f]/.test(v)))
    )
      fail("invalid_fields");
    if (form.getAll(key).length > 1) fail("invalid_fields");
  }
  for (const k of ["truthConsent", "privacyConsent", "informationConfirmed"])
    if (value(form, k) !== "on") fail("invalid_fields");
  for (const k of ["currentStudent", "siblings", "socialSupport", "workArea"])
    if (!["Sim", "Não"].includes(value(form, k))) fail("invalid_fields");
  if (
    !["Pais", "Pai", "Mãe", "Outro responsável"].includes(
      value(form, "guardianMode"),
    )
  )
    fail("invalid_fields");
  if (!email(value(form, "confirmationEmail"))) fail("invalid_email");
  if (
    !date(value(form, "birthDate")) ||
    value(form, "birthDate") > new Date().toISOString().slice(0, 10) ||
    !date(value(form, "childCitizenCardExpiry"))
  )
    fail("invalid_fields");
  if (
    !/^\d{9}$/.test(value(form, "childTaxNumber")) ||
    !/^\d{11}$/.test(value(form, "childSocialSecurity")) ||
    !/^\d{4}-\d{3}$/.test(value(form, "childPostalCode"))
  )
    fail("invalid_fields");
  const response = registration.responses.find(
    (r) => r.code === value(form, "socialResponse"),
  );
  if (!response) fail("invalid_response");
  const guardianCount = value(form, "guardianMode") === "Pais" ? 2 : 1,
    householdCount = Number(value(form, "householdCount"));
  if (
    !Number.isInteger(householdCount) ||
    householdCount < 1 ||
    householdCount > 10
  )
    fail("invalid_fields");
  for (let i = 0; i < guardianCount; i++) {
    const p = `guardian.${i}.`;
    if (
      [
        "role",
        "name",
        "address",
        "postalCode",
        "locality",
        "citizenCard",
        "citizenCardExpiry",
        "taxNumber",
        "phone",
        "email",
      ].some((k) => !value(form, p + k))
    )
      fail("guardian_required");
    if (
      !email(value(form, p + "email")) ||
      !date(value(form, p + "citizenCardExpiry")) ||
      !/^\d{9}$/.test(value(form, p + "taxNumber")) ||
      !/^\d{4}-\d{3}$/.test(value(form, p + "postalCode"))
    )
      fail("invalid_fields");
  }
  const money = (key: string) => {
    const s = value(form, key);
    if (s && !/^\d{1,8}([.,]\d{1,2})?$/.test(s)) fail("invalid_fields");
    const n = cents(s);
    if (!Number.isSafeInteger(n) || n < 0) fail("invalid_fields");
    return n;
  };
  let total = 0;
  for (let i = 0; i < householdCount; i++) {
    if (
      !value(form, `member.${i}.name`) ||
      !value(form, `member.${i}.relationship`)
    )
      fail("household_required");
    total += money(`member.${i}.income`);
  }
  if (Math.abs(total - money("calculatedIncome")) > 1) fail("income_mismatch");
  const required = registration.requiredDocuments.map((k) => `doc.${k}`);
  for (let i = 0; i < guardianCount; i++) required.push(`doc.guardian_${i}_cc`);
  if (value(form, "guardianMode") !== "Pais")
    required.push("doc.parental_responsibility");
  if (value(form, "socialSupport") === "Sim") {
    required.push("doc.social_support");
    if (!value(form, "socialSupportType")) fail("support_type_required");
  }
  if (value(form, "workArea") === "Sim") required.push("doc.work_area");
  for (let i = 0; i < householdCount; i++)
    if (money(`member.${i}.income`))
      required.push(`doc.member_${i}_income_1`, `doc.member_${i}_income_2`);
  for (const k of ["housing", "transport", "health"])
    if (money(`expense.${k}`)) required.push(`doc.expense_${k}`);
  if (value(form, "currentStudent") === "Sim" && !value(form, "studentNumber"))
    fail("student_number_required");
  if (value(form, "siblings") === "Sim" && !value(form, "siblingNumbers"))
    fail("siblings_required");
  if (
    required.some(
      (k) => !(form.get(k) instanceof File) || (form.get(k) as File).size === 0,
    )
  )
    fail("required_documents");
  const files = Array.from(form.entries()).filter(
    (v): v is [string, File] => v[1] instanceof File && v[1].size > 0,
  );
  if (
    files.length > registration.maxFiles ||
    files.reduce((s, [, f]) => s + f.size, 0) > registration.maxTotalFileBytes
  )
    fail("upload_limit");
  for (const [key, file] of files) {
    if (
      !/^doc\.[a-z0-9_]+$/.test(key) ||
      file.name.length > 160 ||
      file.size > registration.maxFileBytes ||
      !registration.allowedMimeTypes.includes(file.type)
    )
      fail("invalid_file");
    if (
      !validSignature(
        file.type,
        new Uint8Array(await file.slice(0, 8).arrayBuffer()),
      )
    )
      fail("invalid_signature");
  }
  return { response: response!, guardianCount, householdCount, files };
}
