'use server';

import { randomBytes } from 'crypto';
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';
import { prisma } from '@/lib/prisma';
import { createSession, clearSession, hashPassword, verifyPassword } from '@/lib/auth';
import { sendPasswordResetEmail } from '@/lib/mail';
import { upsertMailingListEntry } from '@/lib/mailingList';
import { checkRateLimit, getClientIp } from '@/lib/rateLimit';
import { verifyTurnstileToken } from '@/lib/turnstile';

const RESET_TOKEN_DURATION_MS = 60 * 60 * 1000; // 1 hour

// Pulls in any past guest-checkout orders placed under this email so they show
// up in the customer's order history once they have an account.
async function linkGuestOrders(customerId: string, email: string) {
  await prisma.order.updateMany({
    where: { email, customerId: null },
    data: { customerId },
  });
}

function safeNext(next: FormDataEntryValue | null) {
  const path = String(next ?? '');
  return path.startsWith('/') && !path.startsWith('//') ? path : '/account';
}

export async function registerCustomer(formData: FormData) {
  const name = String(formData.get('name') ?? '').trim();
  const email = String(formData.get('email') ?? '').trim().toLowerCase();
  const password = String(formData.get('password') ?? '');
  const confirmPassword = String(formData.get('confirmPassword') ?? '');
  const next = safeNext(formData.get('next'));

  if (!name || !email || !password) redirect('/account/register?error=missing');
  if (password !== confirmPassword) redirect('/account/register?error=mismatch');
  if (password.length < 8) redirect('/account/register?error=weak');

  const ip = getClientIp(headers());
  const turnstileToken = String(formData.get('cf-turnstile-response') ?? '');
  const { success } = await verifyTurnstileToken(turnstileToken, ip);
  if (!success) redirect('/account/register?error=captcha');

  const existing = await prisma.customer.findUnique({ where: { email } });
  if (existing) redirect('/account/register?error=taken');

  const passwordHash = await hashPassword(password);
  const customer = await prisma.customer.create({
    data: { email, passwordHash, name },
  });

  await upsertMailingListEntry({ email: customer.email, name: customer.name, source: 'CUSTOMER' });
  await linkGuestOrders(customer.id, customer.email);
  await createSession(customer.id);
  redirect(next);
}

export async function loginCustomer(formData: FormData) {
  const email = String(formData.get('email') ?? '').trim().toLowerCase();
  const password = String(formData.get('password') ?? '');
  const next = safeNext(formData.get('next'));

  const ip = getClientIp(headers());
  const userAgent = headers().get('user-agent') ?? undefined;

  const { allowed } = await checkRateLimit(`login:customer:${ip}`, 5, 15 * 60 * 1000);
  if (!allowed) {
    // Log rate limited attempt
    await prisma.customerLoginLog.create({
      data: { success: false, email, ipAddress: ip, userAgent },
    });
    redirect(`/account/login?error=rate_limited&next=${encodeURIComponent(next)}`);
  }

  const customer = email ? await prisma.customer.findUnique({ where: { email } }) : null;
  const valid = customer ? await verifyPassword(password, customer.passwordHash) : false;

  if (!customer || !valid) {
    // Log failed login attempt
    await prisma.customerLoginLog.create({
      data: { success: false, email, ipAddress: ip, userAgent },
    });
    redirect(`/account/login?error=invalid&next=${encodeURIComponent(next)}`);
  }

  // Log successful login
  await prisma.customerLoginLog.create({
    data: { success: true, customerId: customer.id, email, ipAddress: ip, userAgent },
  });

  await linkGuestOrders(customer.id, customer.email);
  await createSession(customer.id);
  redirect(next);
}

export async function logoutCustomer() {
  clearSession();
  redirect('/');
}

export async function requestPasswordReset(formData: FormData) {
  const email = String(formData.get('email') ?? '').trim().toLowerCase();
  const customer = email ? await prisma.customer.findUnique({ where: { email } }) : null;

  if (customer) {
    const token = randomBytes(32).toString('hex');
    const expiresAt = new Date(Date.now() + RESET_TOKEN_DURATION_MS);

    await prisma.passwordResetToken.deleteMany({ where: { customerId: customer.id } });
    await prisma.passwordResetToken.create({
      data: { customerId: customer.id, token, expiresAt },
    });

    const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000';
    await sendPasswordResetEmail({
      to: customer.email,
      name: customer.name,
      link: `${siteUrl}/account/reset-password?token=${token}`,
    });
  }

  // Same outcome whether or not the email matched an account, so we don't
  // reveal which emails are registered.
  redirect('/account/forgot-password?sent=1');
}

export async function resetPassword(formData: FormData) {
  const token = String(formData.get('token') ?? '');
  const password = String(formData.get('password') ?? '');
  const confirmPassword = String(formData.get('confirmPassword') ?? '');

  if (!token) redirect('/account/reset-password?error=invalid');
  if (password !== confirmPassword) redirect(`/account/reset-password?token=${token}&error=mismatch`);
  if (password.length < 8) redirect(`/account/reset-password?token=${token}&error=weak`);

  const resetToken = await prisma.passwordResetToken.findUnique({ where: { token } });
  if (!resetToken || resetToken.expiresAt < new Date()) {
    redirect('/account/reset-password?error=expired');
  }

  const passwordHash = await hashPassword(password);
  await prisma.customer.update({
    where: { id: resetToken.customerId },
    data: { passwordHash },
  });
  await prisma.passwordResetToken.delete({ where: { token } });

  await createSession(resetToken.customerId);
  redirect('/account');
}
