/**
 * Configuration, read once at boot and validated loudly.
 *
 * A misconfigured deployment should fail to start with a clear message rather
 * than start successfully and behave subtly wrong. The warnings below are the
 * cases where PIG *can* run but an operator almost certainly did not intend
 * the resulting behaviour.
 */
import { z } from 'zod';

/**
 * Parse a boolean from an environment variable.
 *
 * NOT `z.coerce.boolean()`, which calls `Boolean(value)` — so the string
 * `"false"` becomes `true`, along with `"0"`, `"no"` and `"off"`. Every
 * feature flag set to `false` would silently be on, which is exactly the
 * class of bug that gets discovered in production by reading a startup
 * warning that should not have appeared.
 */
const envBoolean = (defaultValue: boolean) =>
  z
    .string()
    .optional()
    .transform((value) => {
      if (value == null || value.trim() === '') return defaultValue;
      return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase());
    });

const optionalEnvString = (value: unknown) =>
  typeof value === 'string' && value.trim() === '' ? undefined : value;

const schema = z.object({
  DATABASE_URL: z.string().min(1, 'DATABASE_URL is required.'),

  SUPABASE_URL: z.string().url().optional(),

  /*
   * OIDC — the on-premises path.
   *
   * A customer running PIG inside their own network already has an identity
   * provider. Setting PIG_OIDC_ISSUER switches authentication to it and takes
   * precedence over any Supabase values left in the environment file.
   */
  PIG_OIDC_ISSUER: z.string().url().optional(),
  /** Optional. Discovered from the issuer when omitted. */
  PIG_OIDC_JWKS_URI: z.string().url().optional(),
  /** Strongly recommended — see the boot warning. */
  PIG_OIDC_AUDIENCE: z.string().optional(),
  /** Comma-separated, in preference order. Defaults cover most providers. */
  PIG_OIDC_EMAIL_CLAIMS: z.string().optional(),
  SUPABASE_ANON_KEY: z.string().optional(),
  SUPABASE_SERVICE_KEY: z.string().optional(),

  PIG_PORT: z.coerce.number().int().positive().default(8920),
  PIG_PUBLIC_URL: z.string().default('http://localhost:8920'),
  NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),

  PIG_ADMIN_EMAILS: z.string().default(''),
  PIG_INVITE_CODE: z.string().optional(),

  PRIME_API_KEY: z.string().optional(),
  PRIME_API_BASE: z.string().default('https://api.primeintellect.ai'),
  PRIME_SYNC_ENABLED: envBoolean(false),
  PRIME_SYNC_INTERVAL_MINUTES: z.coerce.number().int().positive().default(30),

  /** Base64-encoded 32-byte key. Secrets written in the admin UI require it. */
  PIG_SETTINGS_ENCRYPTION_KEY: z.string().optional(),

  PIGGY_ENABLED: envBoolean(false),
  ANTHROPIC_API_KEY: z.string().optional(),
  PIGGY_MODEL: z.string().default('nvidia/nemotron-3-nano-30b-a3b'),
  PIGGY_INFERENCE_BASE: z.string().url().default('https://api.pinference.ai/api/v1'),
  PIGGY_LEASE_SECONDS: z.coerce.number().int().positive().default(300),
  PIGGY_INTERNAL_URL: z.preprocess(
    (value) => (value === '' ? undefined : value),
    z.string().url().optional(),
  ),
  PIGGY_INTERNAL_TOKEN: z.preprocess(
    (value) => (value === '' ? undefined : value),
    z.string().min(32).optional(),
  ),

  SLACK_BOT_TOKEN: z.string().optional(),
  SLACK_SIGNING_SECRET: z.string().optional(),
  BUZZ_RELAY_URL: z.preprocess(optionalEnvString, z.string().url().optional()),
  BUZZ_PRIVATE_KEY: z.preprocess(optionalEnvString, z.string().min(1).optional()),
  BUZZ_AUTH_TAG: z.preprocess(optionalEnvString, z.string().min(1).optional()),

  NOTION_CLIENT_ID: z.preprocess(optionalEnvString, z.string().min(1).optional()),
  NOTION_CLIENT_SECRET: z.preprocess(optionalEnvString, z.string().min(1).optional()),
  NOTION_REDIRECT_URI: z.preprocess(optionalEnvString, z.string().url().optional()),
  GOOGLE_CLIENT_ID: z.preprocess(optionalEnvString, z.string().min(1).optional()),
  GOOGLE_CLIENT_SECRET: z.preprocess(optionalEnvString, z.string().min(1).optional()),
  GOOGLE_REDIRECT_URI: z.preprocess(optionalEnvString, z.string().url().optional()),
}).superRefine((value, context) => {
  const buzzConfigured = Boolean(
    value.BUZZ_RELAY_URL || value.BUZZ_PRIVATE_KEY || value.BUZZ_AUTH_TAG,
  );
  if (buzzConfigured && !value.BUZZ_RELAY_URL) {
    context.addIssue({
      code: z.ZodIssueCode.custom,
      path: ['BUZZ_RELAY_URL'],
      message: 'BUZZ_RELAY_URL is required when Buzz delivery is configured.',
    });
  }
  if (buzzConfigured && !value.BUZZ_PRIVATE_KEY) {
    context.addIssue({
      code: z.ZodIssueCode.custom,
      path: ['BUZZ_PRIVATE_KEY'],
      message: 'BUZZ_PRIVATE_KEY is required when Buzz delivery is configured.',
    });
  }

  const notionConfigured = Boolean(
    value.NOTION_CLIENT_ID || value.NOTION_CLIENT_SECRET || value.NOTION_REDIRECT_URI,
  );
  for (const key of ['NOTION_CLIENT_ID', 'NOTION_CLIENT_SECRET', 'NOTION_REDIRECT_URI'] as const) {
    if (notionConfigured && !value[key]) {
      context.addIssue({
        code: z.ZodIssueCode.custom,
        path: [key],
        message: `${key} is required when Notion import is configured.`,
      });
    }
  }
  if (notionConfigured && !hasValidEncryptionKey(value.PIG_SETTINGS_ENCRYPTION_KEY)) {
    context.addIssue({
      code: z.ZodIssueCode.custom,
      path: ['PIG_SETTINGS_ENCRYPTION_KEY'],
      message: 'A base64-encoded 32-byte PIG_SETTINGS_ENCRYPTION_KEY is required for Notion OAuth.',
    });
  }

  const googleConfigured = Boolean(
    value.GOOGLE_CLIENT_ID || value.GOOGLE_CLIENT_SECRET || value.GOOGLE_REDIRECT_URI,
  );
  for (const key of ['GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET', 'GOOGLE_REDIRECT_URI'] as const) {
    if (googleConfigured && !value[key]) {
      context.addIssue({
        code: z.ZodIssueCode.custom,
        path: [key],
        message: `${key} is required when Google Sheets import is configured.`,
      });
    }
  }
  if (googleConfigured && !hasValidEncryptionKey(value.PIG_SETTINGS_ENCRYPTION_KEY)) {
    context.addIssue({
      code: z.ZodIssueCode.custom,
      path: ['PIG_SETTINGS_ENCRYPTION_KEY'],
      message: 'A base64-encoded 32-byte PIG_SETTINGS_ENCRYPTION_KEY is required for Google OAuth.',
    });
  }
  if (value.GOOGLE_REDIRECT_URI) {
    try {
      const redirect = new URL(value.GOOGLE_REDIRECT_URI);
      const publicUrl = new URL(value.PIG_PUBLIC_URL);
      if (
        redirect.origin !== publicUrl.origin
        || redirect.pathname !== '/oauth/google/callback'
        || redirect.search
        || redirect.hash
      ) {
        context.addIssue({
          code: z.ZodIssueCode.custom,
          path: ['GOOGLE_REDIRECT_URI'],
          message: 'GOOGLE_REDIRECT_URI must use the PIG_PUBLIC_URL origin and exact /oauth/google/callback path.',
        });
      }
    } catch {
      context.addIssue({
        code: z.ZodIssueCode.custom,
        path: ['GOOGLE_REDIRECT_URI'],
        message: 'GOOGLE_REDIRECT_URI must use the PIG_PUBLIC_URL origin and exact /oauth/google/callback path.',
      });
    }
  }
});

function hasValidEncryptionKey(value: string | undefined): boolean {
  if (!value) return false;
  const decoded = Buffer.from(value, 'base64');
  return decoded.length === 32
    && decoded.toString('base64').replace(/=+$/, '') === value.replace(/=+$/, '');
}

export type Config = z.infer<typeof schema> & {
  adminEmails: string[];
  isProduction: boolean;
};

export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config {
  const parsed = schema.safeParse(env);
  if (!parsed.success) {
    const issues = parsed.error.issues.map((i) => `  ${i.path.join('.')}: ${i.message}`);
    throw new Error(`Invalid configuration:\n${issues.join('\n')}`);
  }

  const adminEmails = parsed.data.PIG_ADMIN_EMAILS.split(',')
    .map((e) => e.trim().toLowerCase())
    .filter(Boolean);

  const config: Config = {
    ...parsed.data,
    adminEmails,
    isProduction: parsed.data.NODE_ENV === 'production',
  };

  warnOnFootguns(config);
  return config;
}

function warnOnFootguns(config: Config): void {
  const warn = (message: string) => console.warn(`[pig] WARNING: ${message}`);

  if (config.adminEmails.length === 0) {
    // Safe default, but worth saying out loud: with no admins nobody can
    // manage invites or settings through the UI.
    warn('PIG_ADMIN_EMAILS is empty — no user will have platform-admin rights.');
  }

  // Keyed on BOTH providers, not just Supabase. An OIDC deployment has
  // authentication and this warning previously claimed it did not — which is
  // worse than saying nothing, because an operator reading "authentication is
  // DISABLED" on a correctly secured install learns to ignore the warnings.
  if (!config.SUPABASE_URL && !config.PIG_OIDC_ISSUER) {
    warn(
      'No identity provider is configured (SUPABASE_URL or PIG_OIDC_ISSUER) — ' +
        'authentication is DISABLED and every request runs as the development ' +
        'user. Never do this in production.',
    );
  }

  if (config.PIG_OIDC_ISSUER && config.SUPABASE_URL) {
    warn(
      'Both PIG_OIDC_ISSUER and SUPABASE_URL are set — OIDC takes precedence and ' +
        'Supabase will not be used for authentication.',
    );
  }

  if (config.PIG_OIDC_ISSUER && !config.PIG_OIDC_AUDIENCE) {
    // Not fatal, because some providers issue single-audience tokens where it
    // adds nothing — but on a shared corporate tenant this is the difference
    // between "a token for PIG" and "a token for anything in the company".
    warn(
      'PIG_OIDC_AUDIENCE is not set. Any token your identity provider issued for ' +
        'ANY application in the same tenant will be accepted here. Set it unless ' +
        'you are certain that is safe.',
    );
  }

  if (config.PIG_OIDC_ISSUER && config.SUPABASE_SERVICE_KEY) {
    warn(
      'SUPABASE_SERVICE_KEY is set while running on OIDC. Self-registration mints ' +
        'Supabase accounts, which an OIDC deployment does not use — unset it and ' +
        'provision users through your identity provider instead.',
    );
  }

  if (config.isProduction && !config.SUPABASE_URL && !config.PIG_OIDC_ISSUER) {
    throw new Error(
      'Refusing to start: NODE_ENV=production with neither SUPABASE_URL nor ' +
        'PIG_OIDC_ISSUER would serve the entire CRM unauthenticated.',
    );
  }

  if (config.PRIME_SYNC_ENABLED && !config.PRIME_API_KEY) {
    warn('PRIME_SYNC_ENABLED is on but PRIME_API_KEY is unset — sync will not run.');
  }

  if (config.PIGGY_ENABLED && (!config.PIGGY_INTERNAL_URL || !config.PIGGY_INTERNAL_TOKEN)) {
    warn(
      'PIGGY_ENABLED is on but the internal URL or token is unset — interactive chat will be unavailable.',
    );
  }

  if (config.SUPABASE_SERVICE_KEY) {
    // Present legitimately for admin provisioning, but it is the most powerful
    // credential in the deployment and most installs do not need it.
    warn(
      'SUPABASE_SERVICE_KEY is set. It is only needed for administrative user ' +
        'provisioning; unset it if you are not using that.',
    );
  }
}
