import type { NextRequest } from 'next/server';
import { toNextJsHandler } from 'better-auth/next-js';

import { auth } from '@/lib/auth';
import { createAuditLog } from '@/lib/audit';
import { logger } from '@/lib/logger';

const upstream = toNextJsHandler(auth);

/**
 * Wrap better-auth's `[...all]` handler so we can stamp the audit log on
 * authentication events. Better-auth itself doesn't fire any callback we
 * can hook on sign-in / sign-out / failed-login - we inspect the route
 * + response status after the upstream handler finishes.
 *
 *   Successful sign-in   → action 'login'  (severity info)
 *   Failed sign-in       → action 'login'  (severity warning, ok=false)
 *   Sign-out             → action 'logout' (userId resolved before cookie
 *                                            is cleared)
 *
 * Audit writes are fire-and-forget (createAuditLog never throws).
 */

interface AuthBody {
  user?: { id?: string; email?: string };
  id?: string;
  email?: string;
}

function clientMeta(req: NextRequest): { ipAddress: string; userAgent: string } {
  const ip =
    req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? req.headers.get('x-real-ip') ?? '';
  return { ipAddress: ip, userAgent: req.headers.get('user-agent') ?? '' };
}

function logSignIn(args: {
  req: NextRequest;
  responseBody: string;
  status: number;
  attemptedEmail: string | null;
}) {
  const meta = clientMeta(args.req);
  let parsed: AuthBody | null = null;
  try {
    parsed = JSON.parse(args.responseBody) as AuthBody;
  } catch {
    /* upstream returned non-JSON */
  }
  const userId = parsed?.user?.id ?? parsed?.id ?? null;
  const email = parsed?.user?.email ?? parsed?.email ?? args.attemptedEmail ?? null;
  const ok = args.status >= 200 && args.status < 300;

  // entityId is text/unbounded but indexed; truncate the attempted-
  // email fallback to keep the row predictably sized when the form
  // sends a giant value. The audit metadata still carries the full
  // original attempted email for forensic context.
  const safeAttempted = (args.attemptedEmail ?? '').slice(0, 256);
  void createAuditLog({
    userId,
    portId: null,
    action: 'login',
    entityType: 'session',
    entityId: userId ?? safeAttempted ?? 'unknown',
    metadata: {
      ok,
      status: args.status,
      attemptedEmail: args.attemptedEmail ?? email ?? null,
    },
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
    severity: ok ? 'info' : 'warning',
    source: 'auth',
  });
}

async function logSignOut(req: NextRequest) {
  const meta = clientMeta(req);
  let userId: string | null = null;
  try {
    const session = await auth.api.getSession({ headers: req.headers });
    userId = session?.user?.id ?? null;
  } catch {
    /* unauthenticated or expired */
  }

  void createAuditLog({
    userId,
    portId: null,
    action: 'logout',
    entityType: 'session',
    entityId: userId ?? 'unknown',
    metadata: {},
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
    severity: 'info',
    source: 'auth',
  });
}

async function withAuthAudit(req: NextRequest): Promise<Response> {
  const url = new URL(req.url);
  const path = url.pathname;
  const isSignIn = path.endsWith('/sign-in/email') || path.endsWith('/sign-in');
  const isSignOut = path.endsWith('/sign-out');

  // Read the request body BEFORE forwarding so we can extract the
  // attempted email even when the credentials are wrong (the upstream
  // handler will consume the body stream and we can't read it twice).
  let attemptedEmail: string | null = null;
  let forwardReq: NextRequest = req;
  if (isSignIn && req.method === 'POST') {
    try {
      const raw = await req.text();
      try {
        attemptedEmail = (JSON.parse(raw) as { email?: string }).email ?? null;
      } catch {
        /* form-encoded or non-JSON */
      }
      // Reconstruct a fresh Request so the upstream handler can read it.
      forwardReq = new Request(req.url, {
        method: req.method,
        headers: req.headers,
        body: raw,
      }) as unknown as NextRequest;
    } catch (err) {
      logger.warn({ err }, 'Failed to read sign-in body for audit');
    }
  }

  // Capture sign-out userId BEFORE the upstream handler clears the cookie.
  const signOutPromise = isSignOut ? logSignOut(req) : null;

  const res = await upstream.POST(forwardReq);

  if (isSignIn) {
    try {
      const body = await res.clone().text();
      logSignIn({ req, responseBody: body, status: res.status, attemptedEmail });
    } catch (err) {
      logger.warn({ err }, 'Failed to capture sign-in response for audit');
    }
  }
  if (signOutPromise) void signOutPromise;

  return res;
}

export const GET = upstream.GET;
export async function POST(req: NextRequest): Promise<Response> {
  return withAuthAudit(req);
}
