/**
 * GET  /api/orgs/[id]/invites — list active invites (admin+).
 * POST /api/orgs/[id]/invites — create an invite (admin+). Two kinds:
 *   { email, role }                          → email pre-invite.
 *   { role, maxUses?, expiresInHours?, label? } → shareable link; the raw token
 *                                                is returned ONCE (H1).
 *
 * The granted role is bounded DB-side and here by the caller's role (H5).
 */

import { requireSession, requireFreshMembership } from '@/lib/auth.js';
import { requireRole } from '@/lib/authz.js';
import { getMembership, getOrgById } from '@/lib/org.js';
import {
  createInvite, createEmailInvite, listInvites, hashToken, encodeInviteBlob,
} from '@/lib/invites.js';
import { getSupabaseAuthConfig } from '@/lib/auth-config.js';

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

/** Resolve the caller's membership in this org and require admin+. Returns the role. */
async function _requireOrgAdmin(req, orgId) {
  const session = await requireSession(req);
  await requireFreshMembership(session, orgId);
  const membership = getMembership(session.user_id, orgId);
  if (!membership) throw { status: 403, error: 'insufficient_role' };
  requireRole({ ...session, org_id: orgId, role: membership.role }, 'admin', orgId);
  return { session, role: membership.role };
}

/**
 * Best-effort cloud mirror of an invite (owner machine, supabase mode). Local is
 * the tested source of truth; the cloud row is what a member redeems against.
 * Non-fatal: a cloud failure never blocks local creation. Integration-deferred.
 */
async function _mirrorInviteToCloud({ orgId, role, email, rawToken, label, maxUses, createdBy, expiresAt }) {
  try {
    const { isSharedCloudReady, getSharedCloudAdmin, ensureCloudOrg } = await import('@/lib/shared-cloud.js');
    if (!isSharedCloudReady()) return;
    // The org must exist in the cloud first — org_invites.org_id FK → organizations.
    // Existing/seeded orgs were never mirrored, so do it here before the invite.
    const org = getOrgById(orgId);
    if (org) await ensureCloudOrg(org);
    const row = {
      org_id: orgId, role, label: label ?? null,
      created_by_user_id: createdBy, expires_at: expiresAt ?? null,
      max_uses: maxUses ?? null,
      email: email ? email : null,
      token_hash: rawToken ? hashToken(rawToken) : null,
    };
    // Let the cloud generate its uuid `id`; key links on token_hash.
    await getSharedCloudAdmin().upsert('org_invites', row, row.token_hash ? { onConflict: 'token_hash' } : undefined);
  } catch (err) {
    console.warn('[invites] cloud mirror failed (local creation succeeded):', err.message);
  }
}

export async function GET(req, { params }) {
  try {
    const { id: orgId } = await params;
    await _requireOrgAdmin(req, orgId);
    return Response.json({ invites: listInvites(orgId) });
  } catch (err) {
    if (err.status) return Response.json({ error: err.error }, { status: err.status });
    console.error('GET /api/orgs/[id]/invites error:', err);
    return Response.json({ error: 'Failed to list invites' }, { status: 500 });
  }
}

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

  try {
    const { id: orgId } = await params;
    const { session, role: callerRole } = await _requireOrgAdmin(req, orgId);
    const role = body.role || 'member';

    // Email pre-invite.
    if (body.email) {
      const invite = createEmailInvite({
        orgId, email: body.email, role,
        createdBy: session.user_id, creatorRole: callerRole, label: body.label ?? null,
      });
      await _mirrorInviteToCloud({
        orgId, role, email: invite.email, label: invite.label,
        createdBy: session.user_id, expiresAt: invite.expires_at,
      });
      return Response.json({ kind: 'email', invite }, { status: 201 });
    }

    // Shareable link.
    const { id, token, invite } = createInvite({
      orgId, role, createdBy: session.user_id, creatorRole: callerRole,
      maxUses: body.maxUses ?? null,
      expiresInHours: body.expiresInHours === undefined ? undefined : body.expiresInHours,
      label: body.label ?? null,
    });
    await _mirrorInviteToCloud({
      orgId, role, rawToken: token, label: invite.label,
      maxUses: invite.max_uses, createdBy: session.user_id, expiresAt: invite.expires_at,
    });

    // Build the paste-one-link join path SERVER-SIDE from the vault-resolved
    // project url + anon key (both public). This removes the old dependency on
    // NEXT_PUBLIC_SUPABASE_* being baked into the admin's browser build — the
    // link now works whenever Supabase is configured at all. The token rides in
    // the FRAGMENT (H1); the client prepends window.location.origin.
    let joinPath = null;
    try {
      const { url, anonKey } = getSupabaseAuthConfig();
      if (url && anonKey) {
        const org = getOrgById(orgId);
        const blob = encodeInviteBlob({ url, anonKey, token, org: org?.name || undefined });
        joinPath = `/join#invite=${blob}`;
      }
    } catch {
      // Non-fatal — the bare token still works via "Join with code".
    }

    // H1: the raw token is returned ONCE and never persisted in raw form.
    return Response.json({ kind: 'link', id, token, invite, joinPath }, { status: 201 });
  } catch (err) {
    if (err.code === 'role_ceiling') {
      return Response.json({ error: 'role_ceiling', message: err.message }, { status: 403 });
    }
    if (err.status) return Response.json({ error: err.error }, { status: err.status });
    console.error('POST /api/orgs/[id]/invites error:', err);
    return Response.json({ error: err.message || 'Failed to create invite' }, { status: 400 });
  }
}
