"use server";

import crypto from "crypto";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { prisma } from "@/lib/prisma";
import { createCustomerSession, getCurrentCustomerId } from "@/lib/customer-auth";
import { hashPassword, verifyPassword } from "@/lib/password";
import { sendPasswordResetEmail } from "@/lib/resend";
import { isRateLimited, recordFailedAttempt, clearAttempts } from "@/lib/rate-limit";
import { getClientIp } from "@/lib/client-ip";
import { isValidEmail, linkGuestOrders, sendLoginCode } from "@/lib/customer-otp";
import { isValidPhone } from "@/lib/phone";
import { SITE } from "@/lib/site";

const MIN_PASSWORD_LENGTH = 8;
const RESET_TOKEN_TTL_MS = 30 * 60 * 1000;
const RESET_RESEND_COOLDOWN_MS = 60 * 1000;

async function getBaseUrl(): Promise<string> {
  const h = await headers();
  const host = h.get("host");
  if (!host) return SITE.siteUrl;
  const proto = h.get("x-forwarded-proto") ?? (host.startsWith("localhost") ? "http" : "https");
  return `${proto}://${host}`;
}

function hashToken(rawToken: string): string {
  return crypto.createHash("sha256").update(rawToken).digest("hex");
}

export async function registerWithPassword(formData: FormData) {
  const name = String(formData.get("name") ?? "").trim();
  const email = String(formData.get("email") ?? "").trim().toLowerCase();
  const phone = String(formData.get("phone") ?? "").trim();
  const password = String(formData.get("password") ?? "");
  const confirmPassword = String(formData.get("confirmPassword") ?? "");

  if (!name) redirect(`/account/register?error=${encodeURIComponent("Enter your name")}`);
  if (!isValidEmail(email)) redirect(`/account/register?error=${encodeURIComponent("Enter a valid email address")}`);
  if (!isValidPhone(phone)) redirect(`/account/register?error=${encodeURIComponent("Enter a valid 10-digit mobile number")}`);
  if (password.length < MIN_PASSWORD_LENGTH) {
    redirect(`/account/register?error=${encodeURIComponent(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`)}`);
  }
  if (password !== confirmPassword) redirect(`/account/register?error=${encodeURIComponent("Passwords don't match")}`);

  const ip = await getClientIp();
  if (await isRateLimited(`register:${ip}`, 15 * 60 * 1000, 10)) {
    redirect(`/account/register?error=${encodeURIComponent("Too many attempts — please try again later")}`);
  }

  const existing = await prisma.customer.findUnique({ where: { email }, omit: { passwordHash: false } });
  if (existing?.passwordHash) {
    await recordFailedAttempt(`register:${ip}`);
    redirect(`/account/register?error=${encodeURIComponent("An account with this email already exists — sign in instead")}`);
  }
  if (existing?.blocked) {
    redirect(`/account/register?error=${encodeURIComponent("This account can't sign in — contact us for help")}`);
  }

  // Phone isn't unique at the database level (it's never been verified), so check by hand — only against
  // other accounts that actually have a password, since an unrelated guest-checkout order reusing the same
  // number shouldn't block anyone from registering.
  const phoneOwner = await prisma.customer.findFirst({
    where: { phone, passwordHash: { not: null }, id: existing ? { not: existing.id } : undefined },
    select: { id: true },
  });
  if (phoneOwner) {
    redirect(`/account/register?error=${encodeURIComponent("This mobile number is already registered to another account")}`);
  }

  const { hash, salt } = hashPassword(password);
  const customer = existing
    ? await prisma.customer.update({
        where: { id: existing.id },
        data: { passwordHash: hash, passwordSalt: salt, name: existing.name ?? name, phone: existing.phone ?? phone },
      })
    : await prisma.customer.create({ data: { email, name, phone, passwordHash: hash, passwordSalt: salt } });

  await sendLoginCode(customer);
}

export async function loginWithPassword(formData: FormData) {
  const identifier = String(formData.get("identifier") ?? "").trim();
  const password = String(formData.get("password") ?? "");
  const genericError = `/account/login?mode=password&error=${encodeURIComponent("Incorrect email/mobile number or password")}`;

  const ip = await getClientIp();
  const ipIdentifier = `customer-login:${ip}`;
  const globalIdentifier = "customer-login:global";
  if ((await isRateLimited(ipIdentifier, 15 * 60 * 1000, 8)) || (await isRateLimited(globalIdentifier, 15 * 60 * 1000, 40))) {
    redirect(`/account/login?mode=password&error=${encodeURIComponent("Too many attempts — please try again later")}`);
  }

  // Email is unique, but phone isn't (it's never been verified, so nothing has ever enforced that) — match on
  // either and check the password against every candidate rather than assuming the first match is the right one.
  const candidates = await prisma.customer.findMany({
    where: { OR: [{ email: identifier.toLowerCase() }, { phone: identifier }] },
    omit: { passwordHash: false, passwordSalt: false },
  });
  const customer = candidates.find((c) => c.passwordHash && c.passwordSalt && verifyPassword(password, c.passwordHash, c.passwordSalt));

  if (!customer) {
    await recordFailedAttempt(ipIdentifier);
    await recordFailedAttempt(globalIdentifier);
    redirect(genericError);
  }
  if (customer.blocked) {
    redirect(`/account/login?mode=password&error=${encodeURIComponent("This account can't sign in — contact us for help")}`);
  }

  await clearAttempts(ipIdentifier);
  await linkGuestOrders(customer.id, customer.email);
  await createCustomerSession(customer.id);
  redirect("/account?success=Logged+in+successfully.");
}

export async function completeProfile(formData: FormData) {
  const customerId = await getCurrentCustomerId();
  if (!customerId) redirect("/account/login");

  const name = String(formData.get("name") ?? "").trim();
  const phone = String(formData.get("phone") ?? "").trim();
  const password = String(formData.get("password") ?? "");
  const confirmPassword = String(formData.get("confirmPassword") ?? "");

  if (!name) redirect(`/account/complete-profile?error=${encodeURIComponent("Enter your name")}`);
  if (!isValidPhone(phone)) redirect(`/account/complete-profile?error=${encodeURIComponent("Enter a valid 10-digit mobile number")}`);
  if (password.length < MIN_PASSWORD_LENGTH) {
    redirect(`/account/complete-profile?error=${encodeURIComponent(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`)}`);
  }
  if (password !== confirmPassword) {
    redirect(`/account/complete-profile?error=${encodeURIComponent("Passwords don't match")}`);
  }

  const phoneOwner = await prisma.customer.findFirst({
    where: { phone, passwordHash: { not: null }, id: { not: customerId } },
    select: { id: true },
  });
  if (phoneOwner) {
    redirect(`/account/complete-profile?error=${encodeURIComponent("This mobile number is already registered to another account")}`);
  }

  const { hash, salt } = hashPassword(password);
  await prisma.customer.update({ where: { id: customerId }, data: { name, phone, passwordHash: hash, passwordSalt: salt } });
  redirect("/account?success=Profile+completed.");
}

export async function requestPasswordReset(formData: FormData) {
  const email = String(formData.get("email") ?? "").trim().toLowerCase();
  if (!isValidEmail(email)) {
    redirect(`/account/forgot-password?error=${encodeURIComponent("Enter a valid email address")}`);
  }

  const ip = await getClientIp();
  if (await isRateLimited(`password-reset:${ip}`, 15 * 60 * 1000, 5)) {
    redirect(`/account/forgot-password?error=${encodeURIComponent("Too many attempts — please try again later")}`);
  }
  await recordFailedAttempt(`password-reset:${ip}`);

  // Always redirect to the same "check your email" state whether or not the account/password exists,
  // so this can't be used to test which emails have accounts.
  const customer = await prisma.customer.findUnique({ where: { email }, omit: { passwordHash: false } });
  if (customer?.passwordHash && !customer.blocked) {
    const recent = await prisma.passwordResetToken.findFirst({ where: { customerId: customer.id }, orderBy: { createdAt: "desc" } });
    if (!recent || Date.now() - recent.createdAt.getTime() >= RESET_RESEND_COOLDOWN_MS) {
      const rawToken = crypto.randomBytes(32).toString("hex");
      await prisma.passwordResetToken.create({
        data: { customerId: customer.id, tokenHash: hashToken(rawToken), expiresAt: new Date(Date.now() + RESET_TOKEN_TTL_MS) },
      });
      const baseUrl = await getBaseUrl();
      try {
        await sendPasswordResetEmail(email, `${baseUrl}/account/reset-password?token=${rawToken}`);
      } catch {
        // Swallow — the response is identical either way so this can't leak whether sending succeeded.
      }
    }
  }

  redirect("/account/forgot-password?sent=1");
}

export async function resetPassword(formData: FormData) {
  const token = String(formData.get("token") ?? "").trim();
  const password = String(formData.get("password") ?? "");
  const confirmPassword = String(formData.get("confirmPassword") ?? "");
  const invalidLink = () => redirect(`/account/forgot-password?error=${encodeURIComponent("This reset link is invalid or has expired — request a new one")}`);

  if (!token) invalidLink();
  if (password.length < MIN_PASSWORD_LENGTH) {
    redirect(`/account/reset-password?token=${encodeURIComponent(token)}&error=${encodeURIComponent(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`)}`);
  }
  if (password !== confirmPassword) {
    redirect(`/account/reset-password?token=${encodeURIComponent(token)}&error=${encodeURIComponent("Passwords don't match")}`);
  }

  const resetToken = await prisma.passwordResetToken.findFirst({
    where: { tokenHash: hashToken(token), consumedAt: null, expiresAt: { gt: new Date() } },
  });
  if (!resetToken) invalidLink();

  const { hash, salt } = hashPassword(password);
  await prisma.$transaction([
    prisma.customer.update({ where: { id: resetToken!.customerId }, data: { passwordHash: hash, passwordSalt: salt } }),
    prisma.passwordResetToken.updateMany({
      where: { customerId: resetToken!.customerId, consumedAt: null },
      data: { consumedAt: new Date() },
    }),
  ]);

  await createCustomerSession(resetToken!.customerId);
  redirect("/account?success=Password+reset+successfully.");
}
