/**
 * Typed configuration loader. Pure over an env object: no reads of process.env
 * here, so tests are deterministic. Fail fast AND clear: every problem is
 * collected, then a single ConfigError is thrown. Error messages name variable
 * NAMES only, never values, so secrets cannot leak.
 */

export type StringRecord = Readonly<Record<string, string | undefined>>;

export type NodeEnv = 'development' | 'test' | 'production';

export interface AppConfig {
  nodeEnv: NodeEnv;
  port: number;
  host: string;
  logLevel: string;
  databaseUrl: string;
  redisUrl?: string;
  /** Session cookie Secure flag. Default true; set COOKIE_SECURE=false for local http dev. */
  cookieSecure: boolean;
  /** Initial flag state parsed from FLAG_* vars. */
  flags: Readonly<Record<string, boolean>>;
}

export class ConfigError extends Error {
  constructor(public readonly problems: ReadonlyArray<string>) {
    super(`Invalid configuration:\n- ${problems.join('\n- ')}`);
    this.name = 'ConfigError';
  }
}

const NODE_ENVS: ReadonlyArray<NodeEnv> = ['development', 'test', 'production'];

function parseBoolFlag(varName: string, raw: string, problems: string[]): boolean | undefined {
  const normalized = raw.trim().toLowerCase();
  if (normalized === 'true') return true;
  if (normalized === 'false') return false;
  problems.push(`${varName} must be "true" or "false"`);
  return undefined;
}

export function loadConfig(env: StringRecord): AppConfig {
  const problems: string[] = [];

  const databaseUrl = env.DATABASE_URL;
  if (!databaseUrl) {
    problems.push('DATABASE_URL is required');
  }

  let port = 3000;
  const rawPort = env.PORT;
  if (rawPort !== undefined && rawPort !== '') {
    const parsed = Number(rawPort);
    if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
      problems.push('PORT must be an integer between 1 and 65535');
    } else {
      port = parsed;
    }
  }

  const host = env.HOST && env.HOST !== '' ? env.HOST : '0.0.0.0';
  const logLevel = env.LOG_LEVEL && env.LOG_LEVEL !== '' ? env.LOG_LEVEL : 'info';

  let nodeEnv: NodeEnv = 'development';
  const rawNodeEnv = env.NODE_ENV;
  if (rawNodeEnv !== undefined && rawNodeEnv !== '') {
    if ((NODE_ENVS as ReadonlyArray<string>).includes(rawNodeEnv)) {
      nodeEnv = rawNodeEnv as NodeEnv;
    } else {
      problems.push('NODE_ENV must be one of: development, test, production');
    }
  }

  const redisUrl = env.REDIS_URL && env.REDIS_URL !== '' ? env.REDIS_URL : undefined;

  let cookieSecure = true;
  const rawCookieSecure = env.COOKIE_SECURE;
  if (rawCookieSecure !== undefined && rawCookieSecure !== '') {
    const normalized = rawCookieSecure.trim().toLowerCase();
    if (normalized === 'true') cookieSecure = true;
    else if (normalized === 'false') cookieSecure = false;
    else problems.push('COOKIE_SECURE must be "true" or "false"');
  }

  const flags: Record<string, boolean> = {};
  for (const [key, raw] of Object.entries(env)) {
    if (!key.startsWith('FLAG_') || raw === undefined) continue;
    const name = key.slice('FLAG_'.length).toLowerCase();
    const value = parseBoolFlag(key, raw, problems);
    if (value !== undefined) {
      flags[name] = value;
    }
  }

  if (problems.length > 0) {
    throw new ConfigError(problems);
  }

  return {
    nodeEnv,
    port,
    host,
    logLevel,
    databaseUrl: databaseUrl as string,
    redisUrl,
    cookieSecure,
    flags,
  };
}
