/**
 * Server-side sign-in endpoint that accepts an email-or-username
 * `identifier`. The username → email resolution happens entirely server-
 * side, so the canonical email is never disclosed to the browser. This
 * closes the username-enumeration vector that the old
 * `/api/auth/resolve-identifier` endpoint left open (it echoed the real
 * email on a hit; a synthetic `@auth.invalid` email on a miss was
 * trivially distinguishable from a real one by domain).
 *
 * The endpoint POSTs to better-auth's `/api/auth/sign-in/email`
 * downstream so the response shape (cookies + JSON body) matches what
 * the existing client expects.
 */
import { NextResponse, type NextRequest } from 'next/server';
import { sql, eq } from 'drizzle-orm';

import { db } from '@/lib/db';
import { user, userProfiles } from '@/lib/db/schema/users';
import { checkRateLimit, rateLimitHeaders, rateLimiters } from '@/lib/rate-limit';

function clientIp(req: NextRequest): string {
  return (
    req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ??
    req.headers.get('x-real-ip') ??
    'unknown'
  );
}

async function resolveToEmail(identifier: string): Promise<string | null> {
  const raw = identifier.trim();
  if (!raw) return null;
  if (raw.includes('@')) return raw;
  const normalized = raw.toLowerCase();
  const rows = await db
    .select({ email: user.email })
    .from(userProfiles)
    .innerJoin(user, eq(userProfiles.userId, user.id))
    .where(sql`LOWER(${userProfiles.username}) = ${normalized}`)
    .limit(1);
  return rows[0]?.email ?? null;
}

export async function POST(req: NextRequest) {
  // Rate-limit on IP - same 5/15min bucket the sign-in endpoint uses.
  const ip = clientIp(req);
  const rl = await checkRateLimit(ip, rateLimiters.auth);
  if (!rl.allowed) {
    // H-04: RFC 6585 §4 requires Retry-After on 429 so automated clients
    // can back off correctly. rateLimitHeaders only emits the X-RateLimit-*
    // triplet; checkRateLimit's helper enforcePublicRateLimit adds this
    // header, but this route uses checkRateLimit directly so the header
    // has to be added explicitly.
    const retryAfter = Math.max(1, Math.ceil((rl.resetAt - Date.now()) / 1000));
    return NextResponse.json(
      { error: { message: 'Too many attempts. Try again later.' } },
      {
        status: 429,
        headers: { ...rateLimitHeaders(rl), 'Retry-After': String(retryAfter) },
      },
    );
  }

  const body = (await req.json().catch(() => ({}))) as {
    identifier?: string;
    password?: string;
    rememberMe?: boolean;
    callbackURL?: string;
  };
  const identifier = (body.identifier ?? '').trim();
  const password = body.password ?? '';
  if (!identifier || !password) {
    // Match better-auth's invalid-credentials shape so the client can
    // surface a uniform error without distinguishing the failure mode.
    return NextResponse.json(
      { error: { message: 'Invalid credentials', code: 'INVALID_EMAIL_OR_PASSWORD' } },
      { status: 401 },
    );
  }

  const email = await resolveToEmail(identifier);
  // On a username miss we still call better-auth with a guaranteed-fail
  // email so the timing and response shape match the hit-with-wrong-
  // password path. The `.invalid` TLD is reserved by RFC 2606 so no real
  // user could ever match it.
  const effectiveEmail =
    email ?? `${identifier.replace(/[^a-z0-9._-]/gi, '').slice(0, 30) || 'unknown'}@auth.invalid`;

  // Forward to better-auth's existing sign-in endpoint. We construct a
  // fresh Request because Next.js's NextRequest is read-only.
  const url = new URL('/api/auth/sign-in/email', req.url);
  const forwardBody = JSON.stringify({
    email: effectiveEmail,
    password,
    rememberMe: body.rememberMe,
    callbackURL: body.callbackURL,
  });
  const forwardReq = new Request(url.toString(), {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      // Preserve client metadata for audit / rate limiting downstream.
      'x-forwarded-for': req.headers.get('x-forwarded-for') ?? ip,
      'user-agent': req.headers.get('user-agent') ?? '',
      cookie: req.headers.get('cookie') ?? '',
      // CRITICAL: forward Origin + Referer so better-auth's CSRF check
      // passes. Without these the internal call lands as a cross-origin
      // request with no Origin → 403 MISSING_OR_NULL_ORIGIN, and the
      // user sees a generic "Invalid credentials" toast even though
      // the password is right. (Bug surfaced 2026-05-13 testing on
      // 192.168.1.17:3000 from an iPad.)
      ...(req.headers.get('origin') ? { origin: req.headers.get('origin')! } : {}),
      ...(req.headers.get('referer') ? { referer: req.headers.get('referer')! } : {}),
    },
    body: forwardBody,
  });

  const { POST: signInHandler } = await import('@/app/api/auth/[...all]/route');
  return signInHandler(forwardReq as NextRequest);
}
