/**
 * POST /api/auth/join-bootstrap — connect a fresh LOCAL install to an invite's
 * Supabase project (Phase 4, security-sensitive).
 *
 * Accepts either { invite: "<base64url blob>" } or explicit { url, anonKey, token }.
 * This route NEVER accepts or persists a service-role key, only writes url+anon
 * via a dedicated path (NOT the owner/admin-gated /api/auth/config save), runs
 * the SSRF-guarded connection test first, and refuses on anything but a clean
 * local install (H6).
 *
 * Pending-invite carry (replaces the old sessionStorage hop): the invite token
 * is stashed in a short-lived httpOnly cookie here, then redeemed AFTER Supabase
 * sign-in by GET /api/orgs/join-complete. httpOnly + SameSite=Lax means the
 * bearer token survives the OAuth login redirect without ever being readable by
 * client JS — strictly safer than the previous sessionStorage stash, which was
 * silently lost on any provider hop or new-tab login.
 *
 * "already connected" is NOT a dead-end: on a shared deployment (the server is
 * already in supabase mode) no per-user bootstrap is needed — we still set the
 * pending cookie and tell the client to proceed to sign-in + redeem.
 */

import { cookies } from 'next/headers';
import { joinBootstrap, decodeInviteBlob } from '@/lib/invites.js';
import { PENDING_INVITE_COOKIE, PENDING_INVITE_MAX_AGE } from '@/lib/pending-invite.js';

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

export async function POST(req) {
  let body;
  try {
    body = await req.json();
  } catch {
    return Response.json({ error: 'Invalid JSON' }, { status: 400 });
  }

  // Defensive: refuse outright if the raw body carries anything that looks like a
  // service-role key, before we touch the decoder or config.
  for (const k of Object.keys(body || {})) {
    if (/service.?role|servicekey/i.test(k)) {
      return Response.json({ error: 'service_key_forbidden', message: 'A service-role key must never be sent here.' }, { status: 400 });
    }
  }

  try {
    let url = body.url;
    let anonKey = body.anonKey;
    let token = body.token;
    if (body.invite) {
      const decoded = decodeInviteBlob(body.invite);
      ({ url, anonKey, token } = decoded);
    }

    if (!token) {
      return Response.json({ error: 'missing_token', message: 'This invite is missing its token.' }, { status: 400 });
    }

    // Stash the bearer token in an httpOnly cookie so it survives the upcoming
    // sign-in redirect. Set regardless of whether bootstrap is needed.
    const secure = (() => {
      try { return new URL(req.url).protocol === 'https:'; } catch { return false; }
    })();
    (await cookies()).set(PENDING_INVITE_COOKIE, token, {
      httpOnly: true,
      sameSite: 'lax',
      secure,
      path: '/',
      maxAge: PENDING_INVITE_MAX_AGE,
    });

    let out;
    try {
      out = await joinBootstrap({ url, anonKey, token });
    } catch (err) {
      // Shared deployment / already-connected machine: no per-user bootstrap is
      // possible, but the cookie is set and the join can still complete via
      // sign-in + redeem. Treat as success.
      if (err.code === 'already_supabase') {
        return Response.json({ ok: true, already: true });
      }
      throw err;
    }

    return Response.json({ ok: true, connected: true, host: out.host });
  } catch (err) {
    const map = {
      service_key_forbidden: 400,
      bad_blob: 400,
      bad_url: 400,
      missing_token: 400,
      bad_request: 400,
      connection_failed: 502,
    };
    if (err.code && map[err.code]) {
      return Response.json({ error: err.code, message: err.message }, { status: map[err.code] });
    }
    console.error('POST /api/auth/join-bootstrap error:', err);
    return Response.json({ error: 'bootstrap_failed', message: 'Could not connect to that project.' }, { status: 500 });
  }
}
