"use server";

import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { eq } from "drizzle-orm";
import { db } from "@/lib/db/client";
import { users, applications } from "@/lib/db/schema";
import { applicationSchema } from "@/lib/membership/application-schema";
import { hashPassword } from "@/lib/auth/password";
import { signApprovalToken } from "@/lib/auth/approval-token";
import { sendEmail } from "@/lib/email/send";
import { adminNewApplicationEmail } from "@/lib/email/templates";
import { loadEnv } from "@/lib/env";
import { ipFromHeaders, rateLimit } from "@/lib/rate-limit";
import { saveAvatarUpload } from "@/lib/uploads/avatar";

export type JoinFormState = {
  ok: boolean;
  fieldErrors?: Record<string, string>;
  message?: string;
};

export async function submitApplication(
  _prev: JoinFormState,
  formData: FormData,
): Promise<JoinFormState> {
  const honeypot = formData.get("website");
  if (typeof honeypot === "string" && honeypot.trim().length > 0) {
    redirect("/join/submitted");
  }

  const ip = ipFromHeaders(await headers());
  const limit = rateLimit({
    key: `join:${ip}`,
    max: 3,
    windowMs: 60 * 60 * 1000,
  });
  if (!limit.ok) {
    return {
      ok: false,
      message: "Too many application attempts. Please try again later.",
    };
  }

  // Object.fromEntries() swallows the File under `avatar` — we handle that
  // upload separately below. Also strip empty strings from optional URL
  // fields so Zod's url() doesn't reject them.
  const raw = Object.fromEntries(formData);
  delete (raw as Record<string, unknown>).avatar;
  const parsed = applicationSchema.safeParse(raw);
  if (!parsed.success) {
    const fieldErrors: Record<string, string> = {};
    for (const issue of parsed.error.issues) {
      fieldErrors[issue.path.join(".")] = issue.message;
    }
    return {
      ok: false,
      fieldErrors,
      message: "Please correct the highlighted fields.",
    };
  }
  const data = parsed.data;

  const existing = await db
    .select({ id: users.id })
    .from(users)
    .where(eq(users.email, data.email))
    .limit(1);
  if (existing.length > 0) {
    return {
      ok: false,
      fieldErrors: { email: "An account with this email already exists." },
    };
  }

  const passwordHash = await hashPassword(data.password);

  // Optional avatar — save through Sharp normalisation. Never block sign-up
  // on a bad image; just record null and move on.
  let avatarPath: string | null = null;
  const avatarFile = formData.get("avatar");
  if (avatarFile instanceof File && avatarFile.size > 0) {
    const saved = await saveAvatarUpload(avatarFile);
    if (saved.ok) avatarPath = saved.path;
  }

  const [newUser] = await db
    .insert(users)
    .values({
      email: data.email,
      name: data.name,
      country: data.country,
      institution: data.institution,
      professionalRole: data.professionalRole,
      orcidUrl: data.orcidUrl || null,
      linkedinUrl: data.linkedinUrl || null,
      passwordHash,
      status: "pending",
      role: "user",
      avatarPath,
      bio: data.bio ?? "",
      specialty: data.specialty ?? "",
      yearsExperience: data.yearsExperience ?? null,
      websiteUrl: data.websiteUrl || null,
      twitterUrl: data.twitterUrl || null,
    })
    .returning({ id: users.id });

  const [newApplication] = await db
    .insert(applications)
    .values({
      userId: newUser.id,
      motivationText: data.motivationText,
      status: "pending",
    })
    .returning({ id: applications.id });

  const token = await signApprovalToken(newApplication.id);
  const env = loadEnv();
  const reviewUrl = `${env.nextAuthUrl}/admin/applications/${newApplication.id}?token=${encodeURIComponent(token)}`;

  await sendEmail({
    to: env.adminNotifyEmails,
    ...adminNewApplicationEmail({
      applicantName: data.name,
      applicantEmail: data.email,
      institution: data.institution,
      country: data.country,
      motivation: data.motivationText,
      reviewUrl,
    }),
  });

  redirect("/join/submitted");
}
