import { NextResponse } from 'next/server';
import { and, eq, isNull } from 'drizzle-orm';
import crypto from 'node:crypto';

import { db } from '@/lib/db';
import { user, userEmailChanges } from '@/lib/db/schema/users';
import { createAuditLog } from '@/lib/audit';
import { errorResponse, ValidationError } from '@/lib/errors';
import { env } from '@/lib/env';

/**
 * Public confirmation endpoint - clicked from the email sent to the
 * NEW address. Applies the email change atomically and redirects the
 * user back to /settings with a success flag.
 *
 * No auth wrapper because the email recipient may not be signed in
 * (e.g. they clicked from another device). The token IS the proof.
 */
export async function GET(
  _req: Request,
  context: { params: Promise<{ token: string }> },
): Promise<Response> {
  try {
    const { token } = await context.params;
    if (!token) throw new ValidationError('Missing token');
    const tokenHash = crypto.createHash('sha256').update(token).digest('hex');

    const pending = await db.query.userEmailChanges.findFirst({
      where: and(
        eq(userEmailChanges.confirmTokenHash, tokenHash),
        isNull(userEmailChanges.appliedAt),
        isNull(userEmailChanges.cancelledAt),
      ),
    });
    if (!pending) throw new ValidationError('Token is invalid or already used');
    if (pending.expiresAt.getTime() < Date.now()) {
      throw new ValidationError('Token has expired');
    }

    // Re-check uniqueness right before the swap so a race with another
    // signup doesn't ship two accounts to the same email.
    const conflict = await db.query.user.findFirst({
      where: eq(user.email, pending.newEmail),
    });
    if (conflict && conflict.id !== pending.userId) {
      throw new ValidationError('That email is already in use by another account');
    }

    await db
      .update(user)
      .set({ email: pending.newEmail, emailVerified: true, updatedAt: new Date() })
      .where(eq(user.id, pending.userId));

    await db
      .update(userEmailChanges)
      .set({ appliedAt: new Date() })
      .where(eq(userEmailChanges.id, pending.id));

    void createAuditLog({
      userId: pending.userId,
      portId: null,
      action: 'update',
      entityType: 'user',
      entityId: pending.userId,
      oldValue: { email: pending.oldEmail },
      newValue: { email: pending.newEmail },
      metadata: { type: 'email_change_confirmed', changeId: pending.id },
    });

    const baseUrl = env.APP_URL.replace(/\/+$/, '');
    return NextResponse.redirect(`${baseUrl}/settings?emailChange=confirmed`);
  } catch (error) {
    return errorResponse(error);
  }
}
