"use server";

import { redirect } from "next/navigation";
import { connectDB } from "@/lib/db";
import { Client } from "@/models/Client";
import {
  hashPassword,
  createSession,
  destroySession,
  generateToken,
  createPending2faToken,
  getPending2faPayload,
  destroyPending2faToken,
  createPendingPasswordChangeToken,
  getPendingPasswordChangePayload,
  destroyPendingPasswordChangeToken,
} from "@/lib/auth";
import { checkCredentials } from "@/lib/teamAuth";
import { verifyTotpToken } from "@/lib/totp";
import bcrypt from "bcryptjs";
import { sendMail } from "@/lib/mail";
import { emailShell } from "@/lib/emailTemplate";
import { formatAddress } from "@/lib/orderConstants";

type FormState = { error?: string; success?: boolean; twoFactorRequired?: boolean; mustChangePassword?: boolean };

const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

async function verificationEmailHtml(locale: string, name: string, link: string) {
  const isEn = locale === "en";
  return await emailShell({
    heading: `Hi ${name},`,
    bodyHtml: isEn
      ? `<p style="margin:0;">Please confirm your email address to activate your Rakku Photography account.</p>`
      : `<p style="margin:0;">Bitte bestätige deine E-Mail-Adresse, um dein Rakku-Photography-Konto zu aktivieren.</p>`,
    buttonLabel: isEn ? "Confirm email" : "E-Mail bestätigen",
    buttonUrl: link,
    footerNote: isEn
      ? "This link is valid for 24 hours. If you didn't create this account, you can ignore this email."
      : "Der Link ist 24 Stunden gültig. Falls du dieses Konto nicht erstellt hast, kannst du diese E-Mail ignorieren.",
  });
}

async function resetEmailHtml(locale: string, name: string, link: string) {
  const isEn = locale === "en";
  return await emailShell({
    heading: `Hi ${name},`,
    bodyHtml: isEn
      ? `<p style="margin:0;">Reset your password using the button below.</p>`
      : `<p style="margin:0;">Setze dein Passwort über den Button unten zurück.</p>`,
    buttonLabel: isEn ? "Reset password" : "Passwort zurücksetzen",
    buttonUrl: link,
    footerNote: isEn
      ? "This link is valid for 1 hour. If you didn't request this, you can ignore this email."
      : "Der Link ist 1 Stunde gültig. Falls du das nicht angefordert hast, kannst du diese E-Mail ignorieren.",
  });
}

export async function registerAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const locale: "de" | "en" = formData.get("locale") === "en" ? "en" : "de";
  const firstName = String(formData.get("firstName") || "").trim();
  const lastName = String(formData.get("lastName") || "").trim();
  const salutationRaw = String(formData.get("salutation") || "").trim();
  const salutation = salutationRaw === "herr" || salutationRaw === "frau" || salutationRaw === "divers" ? salutationRaw : "";
  const pronouns = salutation === "divers" ? String(formData.get("pronouns") || "").trim() : "";
  const nickname = String(formData.get("nickname") || "").trim();
  const email = String(formData.get("email") || "").trim().toLowerCase();
  const password = String(formData.get("password") || "");
  const birthDateRaw = String(formData.get("birthDate") || "");
  const phone = String(formData.get("phone") || "").trim();
  const billingStreet = String(formData.get("billingStreet") || "").trim();
  const billingPostCode = String(formData.get("billingPostCode") || "").trim();
  const billingCity = String(formData.get("billingCity") || "").trim();
  const billingCountryCode = String(formData.get("billingCountryCode") || "DE").trim();
  const acceptTerms = formData.get("acceptTerms") === "on";

  if (!firstName || !lastName) {
    return { error: locale === "en" ? "Please enter your first and last name." : "Bitte gib Vor- und Nachnamen ein." };
  }
  if (!salutation) {
    return { error: locale === "en" ? "Please choose a salutation." : "Bitte wähle eine Anrede aus." };
  }
  if (!EMAIL_RE.test(email)) return { error: locale === "en" ? "Invalid email address." : "Ungültige E-Mail-Adresse." };
  if (password.length < 8)
    return { error: locale === "en" ? "Password must be at least 8 characters." : "Passwort muss mindestens 8 Zeichen haben." };
  const birthDate = birthDateRaw ? new Date(birthDateRaw) : null;
  if (!birthDate || Number.isNaN(birthDate.getTime()) || birthDate.getTime() > Date.now()) {
    return { error: locale === "en" ? "Please enter a valid date of birth." : "Bitte gib ein gültiges Geburtsdatum ein." };
  }
  if (phone.replace(/\D/g, "").length < 5) {
    return { error: locale === "en" ? "Please enter a valid phone number." : "Bitte gib eine gültige Telefonnummer ein." };
  }
  if (!billingStreet || !billingPostCode || !billingCity) {
    return { error: locale === "en" ? "Please enter your address." : "Bitte gib deine Anschrift ein." };
  }
  if (!acceptTerms) {
    return {
      error:
        locale === "en"
          ? "Please accept the Terms & Conditions and Privacy Policy."
          : "Bitte akzeptiere die AGB und die Datenschutzerklärung.",
    };
  }

  await connectDB();
  const existing = await Client.findOne({ email });
  if (existing) {
    return {
      error:
        locale === "en"
          ? "An account with this email already exists."
          : "Für diese E-Mail-Adresse existiert bereits ein Konto.",
    };
  }

  const passwordHash = await hashPassword(password);
  const verificationToken = generateToken();
  const verificationTokenExpires = new Date(Date.now() + 24 * 60 * 60 * 1000);
  const name = `${firstName} ${lastName}`.trim();

  await Client.create({
    name,
    firstName,
    lastName,
    salutation,
    pronouns,
    nickname,
    email,
    passwordHash,
    locale,
    birthDate,
    phone,
    billingStreet,
    billingPostCode,
    billingCity,
    billingCountryCode: billingCountryCode || "DE",
    address: formatAddress({ street: billingStreet, postCode: billingPostCode, city: billingCity, countryCode: billingCountryCode }),
    verified: false,
    verificationToken,
    verificationTokenExpires,
    termsAcceptedAt: new Date(),
  });

  const link = `${process.env.SITE_URL}/${locale}/verify/${verificationToken}`;
  await sendMail(
    email,
    locale === "en" ? "Confirm your email — Rakku Photography" : "E-Mail bestätigen — Rakku Photography",
    await verificationEmailHtml(locale, name, link)
  );

  return { success: true };
}

// Login-Schutz gegen Brute-Force (Lockout-Zähler etc.) lebt jetzt in
// checkCredentials (src/lib/teamAuth.ts) — von hier und der Team-App-API
// gemeinsam genutzt, siehe Kommentar dort.
export async function loginAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const locale = String(formData.get("locale") || "de");
  const email = String(formData.get("email") || "").trim().toLowerCase();
  const password = String(formData.get("password") || "");
  // Nur von der dedizierten Team-App-Login-Seite gesetzt (/team/login) — ein
  // Kundenkonto soll dort gar nicht erst eine Session bekommen, auch wenn die
  // Zugangsdaten stimmen.
  const staffOnly = formData.get("staffOnly") === "1";

  const result = await checkCredentials(email, password, { requireStaff: staffOnly });

  if (!result.ok) {
    const messages: Record<typeof result.reason, string> = {
      invalid: locale === "en" ? "Incorrect email or password." : "E-Mail oder Passwort falsch.",
      locked:
        locale === "en"
          ? `Too many failed attempts — please try again in ${"minutesLeft" in result ? result.minutesLeft : ""} min.`
          : `Zu viele Fehlversuche — bitte in ${"minutesLeft" in result ? result.minutesLeft : ""} Min. erneut versuchen.`,
      unverified:
        locale === "en"
          ? "Please confirm your email address first — check your inbox."
          : "Bitte bestätige zuerst deine E-Mail-Adresse — schau in dein Postfach.",
      "staff-only": "Dieser Zugang ist nur für Mitarbeiter-Konten.",
    };
    return { error: messages[result.reason] };
  }

  const next = String(formData.get("next") || "");

  // Vor 2FA geprüft — ein frisch vom Team angelegtes Mitarbeiter-Konto mit
  // Übergangspasswort kann ohnehin noch kein 2FA eingerichtet haben, aber
  // bei einem nachträglich erzwungenen Wechsel (Bestandskonto) soll der
  // Passwort-Wechsel trotzdem vor einem eventuellen 2FA-Schritt kommen.
  if (result.status === "password-change-required") {
    await createPendingPasswordChangeToken(result.clientId, next, locale);
    return { mustChangePassword: true };
  }

  if (result.status === "2fa-required") {
    await createPending2faToken(result.clientId, next, locale);
    return { twoFactorRequired: true };
  }

  await createSession(String(result.client._id));
  if (next.startsWith("/")) redirect(next);
  redirect(result.client.role === "staff" ? `/${locale}/admin` : `/${locale}/account`);
}

export async function verifyTotpLoginAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const pending = await getPending2faPayload();
  if (!pending) return { error: "Die Anmeldung ist abgelaufen — bitte erneut einloggen." };

  const rawCode = String(formData.get("code") || "").trim();
  if (!rawCode) return { error: "Bitte einen Code eingeben." };

  await connectDB();
  const client = await Client.findById(pending.clientId);
  if (!client || !client.totpEnabled || !client.totpSecret) {
    await destroyPending2faToken();
    return { error: "Die Anmeldung ist abgelaufen — bitte erneut einloggen." };
  }

  const normalizedCode = rawCode.replace(/\s+/g, "");
  let valid = verifyTotpToken(client.totpSecret, normalizedCode);

  if (!valid && /^[A-F0-9]{5}-[A-F0-9]{5}$/i.test(normalizedCode)) {
    for (let i = 0; i < client.totpRecoveryCodeHashes.length; i++) {
      if (await bcrypt.compare(normalizedCode.toUpperCase(), client.totpRecoveryCodeHashes[i])) {
        client.totpRecoveryCodeHashes.splice(i, 1);
        await client.save();
        valid = true;
        break;
      }
    }
  }

  if (!valid) return { error: "Code ungültig." };

  await destroyPending2faToken();

  // Defensive Symmetrie zu loginAction — betrifft in der Praxis nur ein
  // nachträglich für ein bereits-2FA-aktives Bestandskonto erzwungenes
  // Passwort-Reset (ein frisches Konto mit Übergangspasswort kann noch kein
  // 2FA eingerichtet haben, siehe Kommentar dort).
  if (client.mustChangePassword) {
    await createPendingPasswordChangeToken(String(client._id), pending.next, pending.locale);
    return { mustChangePassword: true };
  }

  await createSession(String(client._id));
  if (pending.next.startsWith("/")) redirect(pending.next);
  redirect(client.role === "staff" ? `/${pending.locale}/admin` : `/${pending.locale}/account`);
}

export async function completeForcedPasswordChangeAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const pending = await getPendingPasswordChangePayload();
  if (!pending) return { error: "Die Anmeldung ist abgelaufen — bitte erneut einloggen." };

  const isEn = pending.locale === "en";
  const password = String(formData.get("password") || "");
  const confirmPassword = String(formData.get("confirmPassword") || "");

  if (password.length < 8) {
    return { error: isEn ? "Password must be at least 8 characters." : "Passwort muss mindestens 8 Zeichen haben." };
  }
  if (password !== confirmPassword) {
    return { error: isEn ? "The passwords don't match." : "Die Passwörter stimmen nicht überein." };
  }

  await connectDB();
  const client = await Client.findById(pending.clientId);
  if (!client) {
    await destroyPendingPasswordChangeToken();
    return { error: "Die Anmeldung ist abgelaufen — bitte erneut einloggen." };
  }

  client.passwordHash = await hashPassword(password);
  client.mustChangePassword = false;
  await client.save();

  await destroyPendingPasswordChangeToken();
  await createSession(String(client._id));
  if (pending.next.startsWith("/")) redirect(pending.next);
  redirect(client.role === "staff" ? `/${pending.locale}/admin` : `/${pending.locale}/account`);
}

export async function logoutAction(locale: string) {
  await destroySession();
  redirect(`/${locale}`);
}

// Für die Team-App (Electron) und den Admin-Bereich allgemein — landet zurück
// auf der dedizierten Team-Login-Seite statt der öffentlichen Startseite wie
// logoutAction, damit sich der App-Charakter nicht bei jedem Logout auflöst.
export async function adminLogoutAction() {
  await destroySession();
  redirect(`/team/login`);
}

export async function requestPasswordResetAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const locale = String(formData.get("locale") || "de");
  const email = String(formData.get("email") || "").trim().toLowerCase();

  await connectDB();
  const client = await Client.findOne({ email });

  // Immer Erfolg melden, unabhängig davon ob die E-Mail existiert — verhindert
  // dass Angreifer über diese Seite herausfinden können, welche E-Mails registriert sind.
  if (client) {
    const passwordResetToken = generateToken();
    client.passwordResetToken = passwordResetToken;
    client.passwordResetExpires = new Date(Date.now() + 60 * 60 * 1000);
    await client.save();

    const link = `${process.env.SITE_URL}/${locale}/reset-password/${passwordResetToken}`;
    await sendMail(
      email,
      locale === "en" ? "Reset your password — Rakku Photography" : "Passwort zurücksetzen — Rakku Photography",
      await resetEmailHtml(locale, client.name, link)
    );
  }

  return { success: true };
}

export async function resetPasswordAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const locale = String(formData.get("locale") || "de");
  const token = String(formData.get("token") || "");
  const password = String(formData.get("password") || "");

  if (password.length < 8)
    return { error: locale === "en" ? "Password must be at least 8 characters." : "Passwort muss mindestens 8 Zeichen haben." };

  await connectDB();
  const client = await Client.findOne({
    passwordResetToken: token,
    passwordResetExpires: { $gt: new Date() },
  });

  if (!client) {
    return {
      error:
        locale === "en"
          ? "This link is invalid or has expired."
          : "Dieser Link ist ungültig oder abgelaufen.",
    };
  }

  client.passwordHash = await hashPassword(password);
  client.passwordResetToken = null;
  client.passwordResetExpires = null;
  await client.save();

  return { success: true };
}
