import { NextResponse } from "next/server";
import {
  createUser,
  findUserByAppleId,
  findUserByEmail,
  isUniqueViolation,
  linkAppleAccount,
  markEmailVerified,
} from "@/lib/auth/accounts";
import { AppleTokenError, verifyAppleIdentityToken } from "@/lib/auth/apple";
import { appleSignIn, isMobileClientRequest } from "@/lib/auth/config";
import { requireDatabase } from "@/lib/auth/http";
import { clientIp, rateLimit } from "@/lib/auth/rateLimit";
import { createSession } from "@/lib/auth/session";
import { logSecurityEventAsync, SecurityEventType } from "@/lib/auth/securityEvents";
import { requireCsrf } from "@/lib/auth/csrf";
import { MAX_JSON_BODY_BYTES } from "@/lib/limits";
import { readJsonSized } from "@/lib/http/requestSize";
import { sanitizeText } from "@/lib/ingest/sanitize";

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

interface AppleSignInBody {
  /** The JWT from `ASAuthorizationAppleIDCredential.identityToken`. */
  identityToken?: string;
  /**
   * `ASAuthorizationAppleIDCredential.fullName`, only ever non-null on the
   * user's FIRST authorization with this app — Apple never sends it again on
   * later sign-ins. Used only as a display name for a brand-new account;
   * never trusted for anything security-relevant (the verified token's own
   * `sub`/`email` claims are the only claims that matter for identity).
   */
  fullName?: string | null;
}

function errorResponse(code: string, status: number) {
  return NextResponse.json({ error: code }, { status });
}

/**
 * `POST /api/auth/apple` — native "Sign in with Apple" for the iOS app.
 *
 * Unlike `/api/auth/google/callback`, this is a single request/response, not
 * a redirect flow: the client already completed the on-device Apple
 * authorization (via `AuthenticationServices`) and hands over the resulting
 * identity token, which this route verifies (see `lib/auth/apple.ts`) and
 * turns into a session — exactly the way `/api/auth/login` does, including
 * returning the raw token in the body for `X-Client: ios` callers (see
 * `isMobileClientRequest`). There is currently no web client for this route;
 * it is exercised exclusively by the iOS app's `AppleSignInButton`.
 *
 * Account resolution mirrors the Google callback exactly:
 *   1. Known Apple identity (`sub`) → sign in.
 *   2. Same email already registered → link Apple to that account (Apple has
 *      already proven ownership) and confirm any still-pending signup.
 *   3. Otherwise → create a pre-verified account.
 */
export async function POST(request: Request) {
  const dbDown = await requireDatabase();
  if (dbDown) return dbDown;

  const csrfBlocked = requireCsrf(request);
  if (csrfBlocked) return csrfBlocked;

  const ip = clientIp(request);
  const userAgent = request.headers.get("user-agent");
  const ipLimit = rateLimit(`apple_signin:ip:${ip}`, 20, 15 * 60 * 1000);
  if (!ipLimit.allowed) {
    return NextResponse.json(
      { error: "rate_limited", retryAfter: ipLimit.retryAfter },
      { status: 429, headers: { "Retry-After": String(ipLimit.retryAfter) } }
    );
  }

  const parsed = await readJsonSized<AppleSignInBody>(request, MAX_JSON_BODY_BYTES);
  if (!parsed.ok) return parsed.response;
  const body = parsed.body;
  if (!body?.identityToken) return errorResponse("invalid_request", 400);

  const failure = (code: string, reason: string, email?: string | null) => {
    logSecurityEventAsync({
      type: SecurityEventType.APPLE_SIGNIN_FAILED,
      email: email ?? null,
      ip,
      userAgent,
      metadata: { reason, route: "/api/auth/apple" },
    });
    return errorResponse(code, 401);
  };

  let identity;
  try {
    identity = await verifyAppleIdentityToken(body.identityToken, appleSignIn.bundleId);
  } catch (error) {
    const reason = error instanceof AppleTokenError ? error.message : "verify_failed";
    console.error("[auth] apple identity token verification failed", error);
    return failure("apple_failed", reason);
  }

  try {
    let user = await findUserByAppleId(identity.sub);

    if (!user) {
      // A first-time sign-in with no verified email is not something Apple
      // is expected to produce in the native flow, but refuse cleanly
      // rather than creating an account with no way to contact its owner.
      if (!identity.email || !identity.emailVerified) {
        return failure("apple_email_missing", "no_verified_email");
      }

      const existing = await findUserByEmail(identity.email);

      if (existing) {
        await linkAppleAccount(existing.id, identity.sub);
        if (!existing.emailVerifiedAt) await markEmailVerified(existing.id);
        user = existing;
      } else {
        const name = body.fullName ? sanitizeText(body.fullName, 160) : null;
        try {
          user = await createUser({
            email: identity.email,
            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(identity.email);
          if (!raced) throw error;
          user = raced;
        }
        await linkAppleAccount(user.id, identity.sub);
      }
    }

    const isMobile = isMobileClientRequest(request);
    const { token, expiresAt } = await createSession(
      user.id,
      { remember: true, userAgent, ip },
      { issueCookie: !isMobile }
    );

    logSecurityEventAsync({
      type: SecurityEventType.APPLE_SIGNIN_SUCCESS,
      userId: user.id,
      email: user.email ?? identity.email,
      ip,
      userAgent,
      metadata: { route: "/api/auth/apple" },
    });

    return NextResponse.json({
      status: "signed_in",
      user: {
        id: user.id,
        email: user.email,
        name: user.name,
        plan: user.plan,
        onboardingCompletedAt: user.onboardingCompletedAt?.toISOString() ?? null,
      },
      ...(isMobile ? { token, expiresAt: expiresAt.toISOString() } : {}),
    });
  } catch (error) {
    console.error("[auth] apple sign-in failed", error);
    return failure("apple_failed", "server_error");
  }
}
