import { existsSync } from 'node:fs';
import { resolve } from 'node:path';
import { config as loadDotEnv } from 'dotenv';

type AppConfig = {
  databaseUrl: string;
  jwtSecret: string;
  jwtExpiresIn: string;
  jwtRefreshSecret: string;
  jwtRefreshExpiresIn: string;
  port: number;
  frontendOrigin: string;
  corsOrigins: string[];
  publicApiUrl: string;
  redisUrl: string;
  webhookVerifyToken: string;
  webhookSharedSecret: string;
  metaWebhookAppSecret?: string;
  webhookAllowUnsigned: boolean;
  nodeEnv: string;
  isProduction: boolean;
  mailHost?: string;
  mailPort: number;
  mailSecure: boolean;
  mailUser?: string;
  mailPassword?: string;
  mailFrom: string;
  authLoginMaxAttempts: number;
  authLoginWindowMinutes: number;
  authTwoFactorMaxAttempts: number;
  authTwoFactorWindowMinutes: number;
  authPasswordResetMaxAttempts: number;
  authPasswordResetWindowMinutes: number;
};

let cachedConfig: AppConfig | null = null;
const INSECURE_SECRET_PATTERNS = new Set([
  'change-me',
  'change-me-webhook-token',
  'change-me-webhook-secret',
  'supersecretjwt',
  'password',
  'secret',
]);

function loadEnvironmentFiles() {
  const candidatePaths = [
    resolve(process.cwd(), '../.env'),
    resolve(process.cwd(), '.env'),
    resolve(process.cwd(), '../.env.local'),
    resolve(process.cwd(), '.env.local'),
  ];

  for (const path of candidatePaths) {
    if (existsSync(path)) {
      loadDotEnv({ path, override: false, quiet: true });
    }
  }
}

function requireEnv(name: string) {
  const value = process.env[name]?.trim();
  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

function parsePort(value: string | undefined) {
  const parsed = Number(value ?? '3001');
  if (!Number.isInteger(parsed) || parsed <= 0) {
    throw new Error(`Invalid PORT value: ${value}`);
  }

  return parsed;
}

function parseNodeEnv(value: string | undefined) {
  const normalized = value?.trim().toLowerCase() || 'development';
  if (normalized === 'development' || normalized === 'production' || normalized === 'test') {
    return normalized;
  }

  throw new Error(`Invalid NODE_ENV value: ${value}`);
}

function parseBoolean(value: string | undefined, fallback: boolean) {
  if (value === undefined) {
    return fallback;
  }

  const normalized = value.trim().toLowerCase();
  return normalized === '1' || normalized === 'true' || normalized === 'yes';
}

function parseOptionalPort(value: string | undefined, fallback: number) {
  if (!value?.trim()) {
    return fallback;
  }

  const parsed = Number(value);
  if (!Number.isInteger(parsed) || parsed <= 0) {
    throw new Error(`Invalid port value: ${value}`);
  }

  return parsed;
}

function parsePositiveInt(name: string, value: string | undefined, fallback: number) {
  if (!value?.trim()) {
    return fallback;
  }

  const parsed = Number(value);
  if (!Number.isInteger(parsed) || parsed <= 0) {
    throw new Error(`${name} must be a positive integer`);
  }

  return parsed;
}

function parseUrl(name: string, value: string) {
  try {
    return new URL(value);
  } catch {
    throw new Error(`Invalid ${name} URL: ${value}`);
  }
}

function parseOrigins(value: string | undefined) {
  const rawOrigins = value?.trim() || 'http://localhost:3000';
  const origins = rawOrigins
    .split(',')
    .map((origin) => origin.trim())
    .filter(Boolean);

  if (origins.length === 0) {
    throw new Error('FRONTEND_ORIGIN must contain at least one origin');
  }

  origins.forEach((origin) => parseUrl('FRONTEND_ORIGIN', origin));
  return origins;
}

function assertStrongSecret(name: string, value: string, isProduction: boolean) {
  const minimumLength = isProduction ? 32 : 12;
  if (value.length < minimumLength) {
    throw new Error(`${name} must be at least ${minimumLength} characters long`);
  }

  if (isProduction && INSECURE_SECRET_PATTERNS.has(value.toLowerCase())) {
    throw new Error(`${name} uses an insecure placeholder value`);
  }
}

function assertProductionUrl(name: string, value: string, isProduction: boolean) {
  const parsed = parseUrl(name, value);
  if (!isProduction) {
    return;
  }

  if (parsed.protocol !== 'https:') {
    throw new Error(`${name} must use https in production`);
  }
}

function assertProductionMailConfig(config: Pick<AppConfig, 'mailHost' | 'mailUser' | 'mailPassword' | 'mailFrom'>, isProduction: boolean) {
  if (!isProduction) {
    return;
  }

  const hasAnyMailConfig = Boolean(config.mailHost || config.mailUser || config.mailPassword);
  if (!hasAnyMailConfig) {
    return;
  }

  if (!config.mailHost || !config.mailUser || !config.mailPassword) {
    throw new Error('MAIL_HOST, MAIL_USER, and MAIL_PASSWORD must all be set together in production');
  }

  if (config.mailFrom.toLowerCase() === 'no-reply@bizone.id') {
    throw new Error('MAIL_FROM must be set to a real sender address in production');
  }
}

loadEnvironmentFiles();

export function getAppConfig(): AppConfig {
  if (cachedConfig) {
    return cachedConfig;
  }

  const nodeEnv = parseNodeEnv(process.env.NODE_ENV);
  const isProduction = nodeEnv === 'production';
  const corsOrigins = parseOrigins(process.env.FRONTEND_ORIGIN);
  const frontendOrigin = corsOrigins[0];
  const port = parsePort(process.env.PORT);
  const publicApiUrl = process.env.PUBLIC_API_URL?.trim() || `http://localhost:${port}`;
  const jwtSecret = requireEnv('JWT_SECRET');
  const jwtRefreshSecret = requireEnv('JWT_REFRESH_SECRET');
  const webhookVerifyToken = process.env.WEBHOOK_VERIFY_TOKEN?.trim() || 'change-me-webhook-token';
  const webhookSharedSecret = process.env.WEBHOOK_SHARED_SECRET?.trim() || 'change-me-webhook-secret';
  const webhookAllowUnsigned = parseBoolean(
    process.env.WEBHOOK_ALLOW_UNSIGNED,
    nodeEnv !== 'production',
  );

  cachedConfig = {
    databaseUrl: requireEnv('DATABASE_URL'),
    jwtSecret,
    jwtExpiresIn: process.env.JWT_EXPIRES_IN?.trim() || '1d',
    jwtRefreshSecret,
    jwtRefreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN?.trim() || '30d',
    port,
    frontendOrigin,
    corsOrigins,
    publicApiUrl,
    redisUrl: process.env.REDIS_URL?.trim() || 'redis://127.0.0.1:6379',
    webhookVerifyToken,
    webhookSharedSecret,
    metaWebhookAppSecret: process.env.META_WEBHOOK_APP_SECRET?.trim() || undefined,
    webhookAllowUnsigned,
    nodeEnv,
    isProduction,
    mailHost: process.env.MAIL_HOST?.trim() || undefined,
    mailPort: parseOptionalPort(process.env.MAIL_PORT, 465),
    mailSecure: parseBoolean(process.env.MAIL_SECURE, true),
    mailUser: process.env.MAIL_USER?.trim() || undefined,
    mailPassword: process.env.MAIL_PASSWORD?.trim() || undefined,
    mailFrom: process.env.MAIL_FROM?.trim() || process.env.MAIL_USER?.trim() || 'no-reply@bizone.id',
    authLoginMaxAttempts: parsePositiveInt('AUTH_LOGIN_MAX_ATTEMPTS', process.env.AUTH_LOGIN_MAX_ATTEMPTS, 5),
    authLoginWindowMinutes: parsePositiveInt('AUTH_LOGIN_WINDOW_MINUTES', process.env.AUTH_LOGIN_WINDOW_MINUTES, 15),
    authTwoFactorMaxAttempts: parsePositiveInt(
      'AUTH_2FA_MAX_ATTEMPTS',
      process.env.AUTH_2FA_MAX_ATTEMPTS,
      5,
    ),
    authTwoFactorWindowMinutes: parsePositiveInt(
      'AUTH_2FA_WINDOW_MINUTES',
      process.env.AUTH_2FA_WINDOW_MINUTES,
      10,
    ),
    authPasswordResetMaxAttempts: parsePositiveInt(
      'AUTH_PASSWORD_RESET_MAX_ATTEMPTS',
      process.env.AUTH_PASSWORD_RESET_MAX_ATTEMPTS,
      3,
    ),
    authPasswordResetWindowMinutes: parsePositiveInt(
      'AUTH_PASSWORD_RESET_WINDOW_MINUTES',
      process.env.AUTH_PASSWORD_RESET_WINDOW_MINUTES,
      30,
    ),
  };

  assertStrongSecret('JWT_SECRET', cachedConfig.jwtSecret, isProduction);
  assertStrongSecret('JWT_REFRESH_SECRET', cachedConfig.jwtRefreshSecret, isProduction);
  assertStrongSecret('WEBHOOK_VERIFY_TOKEN', cachedConfig.webhookVerifyToken, isProduction);
  assertStrongSecret('WEBHOOK_SHARED_SECRET', cachedConfig.webhookSharedSecret, isProduction);
  assertProductionUrl('PUBLIC_API_URL', cachedConfig.publicApiUrl, isProduction);
  cachedConfig.corsOrigins.forEach((origin) => assertProductionUrl('FRONTEND_ORIGIN', origin, isProduction));

  if (isProduction && cachedConfig.webhookAllowUnsigned) {
    throw new Error('WEBHOOK_ALLOW_UNSIGNED cannot be enabled in production');
  }

  assertProductionMailConfig(cachedConfig, isProduction);

  return cachedConfig;
}
