import { createHash, randomUUID } from "node:crypto";
import { env } from "../../../lib/server-env";
import { registration } from "../../../config/registration.ts";
import { boundedFormData, BodyTooLarge } from "../../../server/security.ts";
import {
  validateApplication,
  value,
  cents,
  ValidationError,
} from "../../../server/registration.ts";
import type { BoundStatement } from "../../../server/database.ts";
export async function POST(request: Request) {
  const uploaded: string[] = [];
  let committed = false;
  let key = "";
  try {
    const form = await boundedFormData(
      request,
      registration.maxTotalFileBytes + 1024 * 1024,
    );
    key = value(form, "idempotencyKey");
    const { response, guardianCount, householdCount, files } =
      await validateApplication(form);
    const existing = await env.DB.prepare(
      "SELECT public_number FROM applications WHERE idempotency_key=?",
    )
      .bind(key)
      .first<{ public_number: string }>();
    if (existing)
      return Response.json({ ok: true, publicNumber: existing.public_number });
    const id = randomUUID(),
      now = new Date().toISOString(),
      publicNumber = `${registration.academicYear.prefix}-${randomUUID().replaceAll("-", "").slice(0, 12).toUpperCase()}`;
    const adminRecipients = (process.env.ADMIN_EMAILS || "")
      .split(",")
      .map((v) => v.trim())
      .filter(Boolean);
    if (!adminRecipients.length) throw new Error("admin_email_missing");
    const statements: BoundStatement[] = [];
    const add = (sql: string, ...args: (string | number | null)[]) =>
      statements.push(env.DB.prepare(sql).bind(...args));
    add(
      "INSERT INTO academic_years(id,name,application_prefix,active,created_at,updated_at) VALUES (?,?,?,1,?,?) ON CONFLICT(id) DO NOTHING",
      registration.academicYear.id,
      registration.academicYear.name,
      registration.academicYear.prefix,
      now,
      now,
    );
    add(
      "INSERT INTO social_responses(id,code,name,active,created_at,updated_at) VALUES (?,?,?,1,?,?) ON CONFLICT(id) DO NOTHING",
      `response-${response.code}`,
      response.code,
      response.name,
      now,
      now,
    );
    add(
      "INSERT INTO applications(id,public_number,academic_year_id,social_response_id,status,idempotency_key,submitted_at,created_at,updated_at) VALUES (?,?,?,?,'SUBMITTED',?,?,?,?)",
      id,
      publicNumber,
      registration.academicYear.id,
      `response-${response.code}`,
      key,
      now,
      now,
      now,
    );
    add(
      "INSERT INTO children(id,application_id,full_name,birth_date,current_student,student_number,address,postal_code,locality,citizen_card_number,citizen_card_expires_at,tax_number,social_security_number,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
      randomUUID(),
      id,
      value(form, "childName"),
      value(form, "birthDate"),
      value(form, "currentStudent") === "Sim" ? 1 : 0,
      value(form, "studentNumber") || null,
      value(form, "childAddress"),
      value(form, "childPostalCode"),
      value(form, "childLocality"),
      value(form, "childCitizenCard"),
      value(form, "childCitizenCardExpiry"),
      value(form, "childTaxNumber"),
      value(form, "childSocialSecurity"),
      now,
      now,
    );
    add(
      "INSERT INTO application_declarations VALUES (?,?,?,?,?,?,?)",
      id,
      value(form, "socialSupportType") || null,
      value(form, "guardianMode"),
      value(form, "workArea") === "Sim" ? 1 : 0,
      now,
      now,
      now,
    );
    if (value(form, "siblings") === "Sim")
      for (const number of value(form, "siblingNumbers")
        .split(",")
        .map((s) => s.trim())
        .filter(Boolean))
        add(
          "INSERT INTO siblings VALUES (?,?,?,?)",
          randomUUID(),
          id,
          number,
          now,
        );
    for (let i = 0; i < guardianCount; i++) {
      const p = `guardian.${i}.`;
      add(
        "INSERT INTO guardians(id,application_id,role,is_education_guardian,full_name,relationship,address,postal_code,locality,citizen_card_number,citizen_card_expires_at,tax_number,phone,email,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
        randomUUID(),
        id,
        value(form, p + "role"),
        i === 0 ? 1 : 0,
        value(form, p + "name"),
        value(form, p + "relationship") || null,
        value(form, p + "address"),
        value(form, p + "postalCode"),
        value(form, p + "locality"),
        value(form, p + "citizenCard"),
        value(form, p + "citizenCardExpiry"),
        value(form, p + "taxNumber"),
        value(form, p + "phone"),
        value(form, p + "email"),
        now,
        now,
      );
    }
    for (let i = 0; i < householdCount; i++) {
      const p = `member.${i}.`;
      add(
        "INSERT INTO household_members VALUES (?,?,?,?,?,?,?,?)",
        randomUUID(),
        id,
        value(form, p + "name"),
        value(form, p + "relationship"),
        cents(value(form, p + "income")),
        i + 1,
        now,
        now,
      );
    }
    for (const type of ["housing", "transport", "health"]) {
      const amount = cents(value(form, `expense.${type}`));
      if (amount)
        add(
          "INSERT INTO expenses VALUES (?,?,?,?,?,?)",
          randomUUID(),
          id,
          type.toUpperCase(),
          amount,
          now,
          now,
        );
    }
    for (const [field, file] of files) {
      const bytes = new Uint8Array(await file.arrayBuffer()),
        storageKey = `applications/${id}/${randomUUID()}`;
      await env.FILES.put(storageKey, bytes);
      uploaded.push(storageKey);
      add(
        "INSERT INTO documents(id,application_id,type,storage_key,original_name,detected_mime_type,size_bytes,sha256,review_status,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,'RECEIVED',?,?)",
        randomUUID(),
        id,
        field.slice(4).toUpperCase(),
        storageKey,
        file.name,
        file.type,
        file.size,
        createHash("sha256").update(bytes).digest("hex"),
        now,
        now,
      );
    }
    add(
      "INSERT INTO application_status_history VALUES (?,?,?,'DRAFT','SUBMITTED','Formulário público',?)",
      randomUUID(),
      id,
      "PUBLIC",
      now,
    );
    add(
      "INSERT INTO outbox_jobs(id,kind,resource_id,status,attempts,available_at,created_at,updated_at) VALUES (?,'GENERATE_APPLICATION_PDF',?,'PENDING',0,?,?,?)",
      randomUUID(),
      id,
      now,
      now,
      now,
    );
    for (const [recipient, template] of [
      [value(form, "confirmationEmail"), "APPLICATION_CONFIRMATION"],
      ...adminRecipients.map((to) => [to, "APPLICATION_INTERNAL_NOTIFICATION"]),
    ]) {
      const communicationId = randomUUID();
      add(
        "INSERT INTO communications(id,application_id,recipient,template_key,status,attempts,created_at,updated_at) VALUES (?,?,?,?,'PENDING',0,?,?)",
        communicationId,
        id,
        recipient,
        template,
        now,
        now,
      );
      add(
        "INSERT INTO outbox_jobs(id,kind,resource_id,status,attempts,available_at,created_at,updated_at) VALUES (?,'SEND_APPLICATION_EMAIL',?,'PENDING',0,?,?,?)",
        randomUUID(),
        communicationId,
        now,
        now,
        now,
      );
    }
    await env.DB.batch(statements);
    committed = true;
    return Response.json(
      { ok: true, publicNumber, confirmationQueued: true },
      { status: 201 },
    );
  } catch (error) {
    if (!committed)
      await Promise.all(
        uploaded.map((key) => env.FILES.delete(key).catch(() => {})),
      );
    if (error instanceof BodyTooLarge)
      return Response.json({ error: "upload_limit" }, { status: 413 });
    if (error instanceof ValidationError)
      return Response.json(
        { error: error.code },
        { status: error.code === "upload_limit" ? 413 : 400 },
      );
    // A concurrent retry may have won the unique idempotency key after our first lookup.
    if (key)
      try {
        const existing = await env.DB.prepare(
          "SELECT public_number FROM applications WHERE idempotency_key=?",
        )
          .bind(key)
          .first<{ public_number: string }>();
        if (existing)
          return Response.json({
            ok: true,
            publicNumber: existing.public_number,
          });
      } catch {}
    return Response.json({ error: "submission_failed" }, { status: 503 });
  }
}
