import { NextResponse } from "next/server";
import { cookies } from "next/headers";
import { timingSafeEqual } from "node:crypto";
import {
  createUser,
  findUserByEmail,
  findUserByGoogleId,
  isUniqueViolation,
  linkGoogleAccount,
  markEmailVerified,
} from "@/lib/auth/accounts";
import { exchangeCodeForTokens, fetchGoogleProfile } from "@/lib/auth/google";
import { createSessionRecord, sessionCookieOptions } from "@/lib/auth/session";
import { clientIp } from "@/lib/auth/rateLimit";
import {
  logSecurityEventAsync,
  SecurityEventType,
} from "@/lib/auth/securityEvents";
import { ensureDatabaseInitialized } from "@/lib/db/init";
import {
  OAUTH_RETURN_COOKIE,
  OAUTH_STATE_COOKIE,
  OAUTH_VERIFIER_COOKIE,
  SESSION_COOKIE,
  cookieSecurityOptions,
  googleOAuth,
} from "@/lib/auth/config";
import { hostIsSiteFirstParty } from "@/lib/seo/site";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

function constantTimeEquals(a: string, b: string): boolean {
  const left = Buffer.from(a);
  const right = Buffer.from(b);
  return left.length === right.length && timingSafeEqual(left, right);
}

type CookieStore = Awaited<ReturnType<typeof cookies>>;

/**
 * Where to send the user right after a successful sign-in.
 *
 * Defaults to `/dashboard` on the current (callback) host, which is the main
 * host. If this sign-in started on a first-party subdomain (app./admin.), the
 * return cookie routes them back there — an app.* origin goes to the
 * dashboard, an admin.* origin to /admin (the admin subdomain has no
 * dashboard route). The target host is re-validated against the first-party
 * host allowlist so the cookie can never be abused for an open redirect.
 */
function resolvePostLoginDestination(
  request: Request,
  cookieStore: CookieStore,
  needsOnboarding: boolean
): URL {
  const returnHost = cookieStore.get(OAUTH_RETURN_COOKIE)?.value;
  if (returnHost && hostIsSiteFirstParty(returnHost)) {
    const path = returnHost.toLowerCase().startsWith("admin.")
      ? "/admin"
      : needsOnboarding
        ? "/dashboard/onboarding"
        : "/dashboard";
    // Protocol-relative: keeps the callback's scheme while swapping the host.
    return new URL(`//${returnHost}${path}`, request.url);
  }
  return new URL(needsOnboarding ? "/dashboard/onboarding" : "/dashboard", request.url);
}

/**
 * Step 2 of Google sign-in.
 *
 * Account resolution, in order:
 *   1. Known Google identity → sign in.
 *   2. Same email already registered → link Google to that account. Google has
 *      proven ownership of the address, so this is the correct outcome and also
 *      confirms a signup that was still pending — one person, one account.
 *   3. Otherwise → create a pre-verified account.
 */
export async function GET(request: Request) {
  const url = new URL(request.url);
  const ip = clientIp(request);
  const userAgent = request.headers.get("user-agent");
  const failure = (code: string, reason?: string, email?: string | null) => {
    logSecurityEventAsync({
      type: SecurityEventType.GOOGLE_SIGNIN_FAILED,
      email: email ?? null,
      ip,
      userAgent,
      metadata: { reason: reason ?? code, stage: code, route: "/api/auth/google/callback" },
    });
    return NextResponse.redirect(new URL(`/auth/login?error=${code}`, request.url));
  };

  if (!googleOAuth.isConfigured) return failure("google_not_configured");

  // The user dismissed Google's consent screen.
  if (url.searchParams.get("error")) return failure("google_denied");

  const code = url.searchParams.get("code");
  const state = url.searchParams.get("state");
  if (!code || !state) return failure("google_failed", "missing_code_or_state");

  const cookieStore = await cookies();
  const expectedState = cookieStore.get(OAUTH_STATE_COOKIE)?.value;
  const verifier = cookieStore.get(OAUTH_VERIFIER_COOKIE)?.value;

  if (!expectedState || !verifier || !constantTimeEquals(state, expectedState)) {
    return failure("google_failed", "state_mismatch");
  }

  if (!(await ensureDatabaseInitialized())) return failure("database_unavailable");

  try {
    const tokens = await exchangeCodeForTokens(code, verifier);
    const profile = await fetchGoogleProfile(tokens.access_token);

    // Google can hand back an address it has not itself confirmed; refuse those
    // rather than treating them as proof of ownership.
    if (!profile.emailVerified) return failure("google_unverified_email", "unverified_email", profile.email);

    let user = await findUserByGoogleId(profile.sub);

    if (!user) {
      const existing = await findUserByEmail(profile.email);

      if (existing) {
        await linkGoogleAccount(existing.id, profile.sub);
        if (!existing.emailVerifiedAt) await markEmailVerified(existing.id);
        user = existing;
      } else {
        try {
          user = await createUser({
            email: profile.email,
            name: profile.name,
            emailVerified: true,
          });
        } catch (error) {
          // Concurrent first-time sign-in for the same address; re-read and link.
          if (!isUniqueViolation(error)) throw error;
          const raced = await findUserByEmail(profile.email);
          if (!raced) throw error;
          user = raced;
        }
        await linkGoogleAccount(user.id, profile.sub);
      }
    }

    const { token, expiresAt } = await createSessionRecord(user.id, {
      remember: true,
      userAgent: request.headers.get("user-agent"),
      ip: clientIp(request),
    });

    logSecurityEventAsync({
      type: SecurityEventType.GOOGLE_SIGNIN_SUCCESS,
      userId: user.id,
      email: user.email ?? profile.email,
      ip,
      userAgent,
      metadata: { route: "/api/auth/google/callback" },
    });

    const response = NextResponse.redirect(
      resolvePostLoginDestination(request, cookieStore, !user.onboardingCompletedAt)
    );
    response.cookies.set(SESSION_COOKIE, token, sessionCookieOptions(expiresAt));
    response.cookies.delete({ name: OAUTH_STATE_COOKIE, ...cookieSecurityOptions() });
    response.cookies.delete({ name: OAUTH_VERIFIER_COOKIE, ...cookieSecurityOptions() });
    response.cookies.delete({ name: OAUTH_RETURN_COOKIE, ...cookieSecurityOptions() });

    return response;
  } catch (error) {
    console.error("[auth] google callback failed", error);
    return failure("google_failed", "exchange_failed");
  }
}
