/**
 * POST /api/orgs/join — redeem a shareable invite token (Phase 3).
 *
 * Requires a valid SESSION (the caller must be signed into Supabase) but NOT an
 * existing membership — this is how a not-yet-member joins. The token arrives in
 * the POST body, NEVER a URL query string (H1: it is a bearer secret and must
 * not hit server/referrer logs). Rate-limited per user + IP to blunt brute force.
 *
 * Redemption path:
 *   • supabase mode (member) → redeem_invite_token() RPC under the member's own
 *     JWT, then mirror the membership locally (service-role key never involved).
 *   • local mode             → lib/invites.redeemInvite against local SQLite.
 */

import { requireSession, getAuthMode } from '@/lib/auth.js';
import { redeemInvite } from '@/lib/invites.js';

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

// ── Lightweight in-process rate limit (sliding window) ─────────────────────────
// Two buckets so a token grinder cannot dodge the limit by self-registering new
// accounts (which mints a fresh user_id): a tight per-(user,ip) bucket AND a
// looser per-ip bucket that is independent of user_id.
const WINDOW_MS = 60_000;
const MAX_PER_USER_IP = 10;
const MAX_PER_IP = 30;
const _attempts = new Map(); // key → number[] (timestamps)

function _bump(key, max, now) {
  const arr = (_attempts.get(key) || []).filter((t) => now - t < WINDOW_MS);
  arr.push(now);
  _attempts.set(key, arr);
  return arr.length > max;
}

function _rateLimited(userId, ip) {
  const now = Date.now();
  // Opportunistic GC so the map cannot grow unbounded.
  if (_attempts.size > 5000) {
    for (const [k, v] of _attempts) {
      if (!v.some((t) => now - t < WINDOW_MS)) _attempts.delete(k);
    }
  }
  const overIp = _bump(`ip:${ip}`, MAX_PER_IP, now);
  const overUser = _bump(`u:${userId}:${ip}`, MAX_PER_USER_IP, now);
  return overIp || overUser;
}

function _clientIp(req) {
  const xff = req?.headers?.get?.('x-forwarded-for');
  return xff ? String(xff).split(',')[0].trim() : 'unknown';
}

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

  try {
    const session = await requireSession(req);

    const token = typeof body?.token === 'string' ? body.token.trim() : '';
    if (!token) return Response.json({ error: 'token is required' }, { status: 400 });

    if (_rateLimited(session.user_id, _clientIp(req))) {
      return Response.json({ error: 'rate_limited', message: 'Too many attempts. Try again shortly.' }, { status: 429 });
    }

    let result;
    if (getAuthMode() === 'supabase' && session.supabase_access_token) {
      const { redeemInviteViaCloud } = await import('@/lib/shared-cloud.js');
      result = await redeemInviteViaCloud(token, session);
    } else {
      result = redeemInvite(token, session.user_id);
    }

    return Response.json({ ok: true, org_id: result.org_id, role: result.role, already_member: result.already_member ?? false });
  } catch (err) {
    if (err.code === 'invalid_token') {
      return Response.json({ error: 'invalid_token', message: 'This invite link is invalid or has expired.' }, { status: 404 });
    }
    if (err.code === 'invite_invalid') {
      return Response.json({ error: 'invite_invalid', message: 'This invite is no longer valid (expired or already used).' }, { status: 410 });
    }
    if (err.status) return Response.json({ error: err.error }, { status: err.status });
    console.error('POST /api/orgs/join error:', err);
    return Response.json({ error: 'Failed to join organization' }, { status: 500 });
  }
}
