// Centralized loader for the privileged Supabase environment variables.
//
// This module is the **single place** in the codebase where
// `SUPABASE_SERVICE_ROLE_KEY` is read from `process.env`. Every privileged
// client factory (`createAdminClient`, `createControlPlaneClient`, and the
// dev-only fallback inside `createControlPlaneReadonlyClient`) delegates to
// this module so a rotation only has to touch one env-reading code path.
//
// Invariants enforced by this module:
//   1. The service-role key is never logged, echoed, or returned in any
//      error message. Errors mention the variable *name* only.
//   2. Functions here never perform I/O. They read `process.env` and return
//      primitive values. All side effects live in the caller.
//   3. `describeEnvState()` never reveals the key value. It returns a
//      presence boolean plus a coarse length bucket (`missing`, `short`,
//      `normal`, `long`) so rotation preflight can distinguish *blank* from
//      *mistakenly-set-to-a-short-string* without exposing the value.
//
// The list of required variables is exported as a frozen constant so
// rotation runbooks, `check:secrets`, and `preflight-rotation` all refer to
// a single source of truth.
//
// **Not covered by this module** (tracked separately in the runbook):
//   - `SUPABASE_MANAGEMENT_API_KEY` / `SUPABASE_ORGANIZATION_ID` —
//     provisioning-only, read exclusively from `lib/provisioning/steps.ts`.
//   - `VERCEL_API_TOKEN` / `VERCEL_TEAM_ID` — provisioning-only, same.
//   - `CONTROL_PLANE_READONLY_KEY` — scoped reader, rotation handled in its
//     own section of the runbook.
//   - `cp_tenant_environments.supabase_service_role_key` (database-stored
//     per-tenant service-role keys) — rotated with the downstream tenant
//     Supabase project; runbook documents the procedure separately.

export const PRIVILEGED_SUPABASE_ENV_VARS = Object.freeze([
  'NEXT_PUBLIC_SUPABASE_URL',
  'SUPABASE_SERVICE_ROLE_KEY',
] as const)

export type PrivilegedSupabaseEnvVar = (typeof PRIVILEGED_SUPABASE_ENV_VARS)[number]

export interface PrivilegedSupabaseEnv {
  url: string
  serviceRoleKey: string
}

/**
 * Returns the privileged Supabase URL + service-role key, or throws with a
 * non-leaking error message if either is missing/blank. Callers should
 * invoke this at the top of any privileged client factory; the error
 * surfaces at application startup or first call, not silently.
 *
 * Safe-by-construction: the error message names the variables but never
 * includes any value.
 */
export function requirePrivilegedSupabaseEnv(): PrivilegedSupabaseEnv {
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL
  const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY

  const missing: string[] = []
  if (!url) missing.push('NEXT_PUBLIC_SUPABASE_URL')
  if (!serviceRoleKey) missing.push('SUPABASE_SERVICE_ROLE_KEY')

  if (missing.length > 0) {
    throw new Error(
      `Missing required privileged Supabase environment variable(s): ${missing.join(', ')}. ` +
        'See docs/security/service-role-key-rotation.md for the list of runtimes that consume these variables.',
    )
  }

  return { url: url!, serviceRoleKey: serviceRoleKey! }
}

/**
 * Cheap predicate for preflight checks. Returns `true` only if both the URL
 * and the service-role key are present and non-empty. Never throws, never
 * reveals the key.
 */
export function hasPrivilegedSupabaseEnv(): boolean {
  return Boolean(process.env.NEXT_PUBLIC_SUPABASE_URL) && Boolean(process.env.SUPABASE_SERVICE_ROLE_KEY)
}

export type EnvPresence = {
  name: string
  present: boolean
  /**
   * Coarse length bucket. Never a real length — the bucket is derived so
   * log scrapers and the preflight CLI cannot be used to fingerprint the
   * secret.
   */
  lengthBucket: 'missing' | 'short' | 'normal' | 'long'
}

function bucketize(value: string | undefined): EnvPresence['lengthBucket'] {
  if (!value) return 'missing'
  if (value.length < 20) return 'short'
  if (value.length < 200) return 'normal'
  return 'long'
}

/**
 * Describe the privileged Supabase env state *without* revealing any value.
 * Used by `scripts/preflight-rotation.mjs` so operators can verify that
 * every runtime they own has picked up the new key without the value ever
 * appearing in a terminal or CI log.
 */
export function describePrivilegedSupabaseEnvState(): EnvPresence[] {
  return PRIVILEGED_SUPABASE_ENV_VARS.map((name) => {
    const value = process.env[name]
    return {
      name,
      present: Boolean(value),
      lengthBucket: bucketize(value),
    }
  })
}
