// src/config/env.ts
/**
 * @file Centralized, schema-validated configuration service.
 * Implements ADR-007: Configuration and Secrets Management.
 *
 * This module parses and validates all environment variables at application startup.
 * If any required configuration is missing or invalid, the application will fail fast
 * with a clear error message.
 *
 * Usage:
 *   import { config } from './config/env';
 *   console.log(config.database.host);
 */
import { z } from 'zod';

// --- Schema Definitions ---

/**
 * Helper to parse string to integer with default.
 * Handles empty strings by treating them as undefined.
 */
const intWithDefault = (defaultValue: number) =>
  z
    .string()
    .optional()
    .transform((val) => (val && val.trim() !== '' ? parseInt(val, 10) : defaultValue))
    .pipe(z.number().int());

/**
 * Helper to parse string to float with default.
 */
const floatWithDefault = (defaultValue: number) =>
  z
    .string()
    .optional()
    .transform((val) => (val && val.trim() !== '' ? parseFloat(val) : defaultValue))
    .pipe(z.number());

/**
 * Helper to parse string 'true'/'false' to boolean.
 */
const booleanString = (defaultValue: boolean) =>
  z
    .string()
    .optional()
    .transform((val) => (val === undefined ? defaultValue : val === 'true'));

/**
 * Database configuration schema.
 */
const databaseSchema = z.object({
  host: z.string().min(1, 'DB_HOST is required'),
  port: intWithDefault(5432),
  user: z.string().min(1, 'DB_USER is required'),
  password: z.string().min(1, 'DB_PASSWORD is required'),
  name: z.string().min(1, 'DB_NAME is required'),
});

/**
 * Redis configuration schema.
 */
const redisSchema = z.object({
  url: z.string().url('REDIS_URL must be a valid URL'),
  password: z.string().optional(),
});

/**
 * Authentication configuration schema.
 */
const authSchema = z.object({
  jwtSecret: z.string().min(32, 'JWT_SECRET must be at least 32 characters for security'),
  jwtSecretPrevious: z.string().optional(), // For secret rotation (ADR-029)
});

/**
 * SMTP/Email configuration schema.
 * All fields are optional - email service degrades gracefully if not configured.
 */
const smtpSchema = z.object({
  host: z.string().optional(),
  port: intWithDefault(587),
  user: z.string().optional(),
  pass: z.string().optional(),
  secure: booleanString(false),
  fromEmail: z.string().email().optional(),
});

/**
 * AI/Gemini configuration schema.
 */
const aiSchema = z.object({
  geminiApiKey: z.string().optional(),
  geminiRpm: intWithDefault(5),
  priceQualityThreshold: floatWithDefault(0.5),
});

/**
 * UPC API configuration schema.
 * External APIs for product lookup by barcode.
 */
const upcSchema = z.object({
  upcItemDbApiKey: z.string().optional(), // UPC Item DB API key (upcitemdb.com)
  barcodeLookupApiKey: z.string().optional(), // Barcode Lookup API key (barcodelookup.com)
});

/**
 * Google services configuration schema.
 */
const googleSchema = z.object({
  mapsApiKey: z.string().optional(),
  clientId: z.string().optional(),
  clientSecret: z.string().optional(),
});

/**
 * Worker concurrency configuration schema.
 */
const workerSchema = z.object({
  concurrency: intWithDefault(1),
  lockDuration: intWithDefault(30000),
  emailConcurrency: intWithDefault(10),
  analyticsConcurrency: intWithDefault(1),
  cleanupConcurrency: intWithDefault(10),
  weeklyAnalyticsConcurrency: intWithDefault(1),
});

/**
 * Server configuration schema.
 */
const serverSchema = z.object({
  nodeEnv: z.enum(['development', 'production', 'test', 'staging']).default('development'),
  port: intWithDefault(3001),
  frontendUrl: z.string().url().optional(),
  baseUrl: z.string().optional(),
  storagePath: z.string().default('/var/www/flyer-crawler.projectium.com/flyer-images'),
});

/**
 * Error tracking configuration schema (ADR-015).
 * Uses Bugsink (Sentry-compatible self-hosted error tracking).
 */
const sentrySchema = z.object({
  dsn: z.string().optional(), // Sentry DSN for backend
  enabled: booleanString(true),
  environment: z.string().optional(),
  debug: booleanString(false),
});

/**
 * Complete environment configuration schema.
 */
const envSchema = z.object({
  database: databaseSchema,
  redis: redisSchema,
  auth: authSchema,
  smtp: smtpSchema,
  ai: aiSchema,
  upc: upcSchema,
  google: googleSchema,
  worker: workerSchema,
  server: serverSchema,
  sentry: sentrySchema,
});

export type EnvConfig = z.infer<typeof envSchema>;

// --- Configuration Loading ---

/**
 * Maps environment variables to the configuration structure.
 * This is the single source of truth for which env vars map to which config keys.
 */
function loadEnvVars(): unknown {
  return {
    database: {
      host: process.env.DB_HOST,
      port: process.env.DB_PORT,
      user: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
      name: process.env.DB_NAME,
    },
    redis: {
      url: process.env.REDIS_URL,
      password: process.env.REDIS_PASSWORD,
    },
    auth: {
      jwtSecret: process.env.JWT_SECRET,
      jwtSecretPrevious: process.env.JWT_SECRET_PREVIOUS,
    },
    smtp: {
      host: process.env.SMTP_HOST,
      port: process.env.SMTP_PORT,
      user: process.env.SMTP_USER,
      pass: process.env.SMTP_PASS,
      secure: process.env.SMTP_SECURE,
      fromEmail: process.env.SMTP_FROM_EMAIL,
    },
    ai: {
      geminiApiKey: process.env.GEMINI_API_KEY,
      geminiRpm: process.env.GEMINI_RPM,
      priceQualityThreshold: process.env.AI_PRICE_QUALITY_THRESHOLD,
    },
    upc: {
      upcItemDbApiKey: process.env.UPC_ITEM_DB_API_KEY,
      barcodeLookupApiKey: process.env.BARCODE_LOOKUP_API_KEY,
    },
    google: {
      mapsApiKey: process.env.GOOGLE_MAPS_API_KEY,
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    },
    worker: {
      concurrency: process.env.WORKER_CONCURRENCY,
      lockDuration: process.env.WORKER_LOCK_DURATION,
      emailConcurrency: process.env.EMAIL_WORKER_CONCURRENCY,
      analyticsConcurrency: process.env.ANALYTICS_WORKER_CONCURRENCY,
      cleanupConcurrency: process.env.CLEANUP_WORKER_CONCURRENCY,
      weeklyAnalyticsConcurrency: process.env.WEEKLY_ANALYTICS_WORKER_CONCURRENCY,
    },
    server: {
      nodeEnv: process.env.NODE_ENV,
      port: process.env.PORT,
      frontendUrl: process.env.FRONTEND_URL,
      baseUrl: process.env.BASE_URL,
      storagePath: process.env.STORAGE_PATH,
    },
    sentry: {
      dsn: process.env.SENTRY_DSN,
      enabled: process.env.SENTRY_ENABLED,
      environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV,
      debug: process.env.SENTRY_DEBUG,
    },
  };
}

/**
 * Validates and parses environment configuration.
 * Throws a descriptive error if validation fails.
 */
function parseConfig(): EnvConfig {
  const rawConfig = loadEnvVars();
  const result = envSchema.safeParse(rawConfig);

  if (!result.success) {
    const errors = result.error.issues.map((issue) => {
      const path = issue.path.join('.');
      return `  - ${path}: ${issue.message}`;
    });

    const errorMessage = [
      '',
      '╔════════════════════════════════════════════════════════════════╗',
      '║           CONFIGURATION ERROR - APPLICATION STARTUP            ║',
      '╚════════════════════════════════════════════════════════════════╝',
      '',
      'The following environment variables are missing or invalid:',
      '',
      ...errors,
      '',
      'Please check your .env file or environment configuration.',
      'See ADR-007 for the complete list of required environment variables.',
      '',
    ].join('\n');

    // In test/staging environment, throw instead of exiting to allow test frameworks to catch
    // and to provide better visibility into config errors during staging deployments
    if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'staging') {
      throw new Error(errorMessage);
    }

    console.error(errorMessage);
    process.exit(1);
  }

  return result.data;
}

// --- Exported Configuration ---

/**
 * The validated application configuration.
 * This is a singleton that is parsed once at module load time.
 *
 * @example
 * ```typescript
 * import { config } from './config/env';
 *
 * // Access database config
 * const pool = new Pool({
 *   host: config.database.host,
 *   port: config.database.port,
 *   user: config.database.user,
 *   password: config.database.password,
 *   database: config.database.name,
 * });
 *
 * // Check environment
 * if (config.server.isProduction) {
 *   // production-only logic
 * }
 * ```
 */
export const config: EnvConfig = parseConfig();

// --- Convenience Helpers ---

/**
 * Returns true if running in production environment.
 */
export const isProduction = config.server.nodeEnv === 'production';

/**
 * Returns true if running in test environment.
 */
export const isTest = config.server.nodeEnv === 'test';

/**
 * Returns true if running in development environment.
 */
export const isDevelopment = config.server.nodeEnv === 'development';

/**
 * Returns true if running in staging environment.
 */
export const isStaging = config.server.nodeEnv === 'staging';

/**
 * Returns true if running in a test-like environment (test or staging).
 * Use this for behaviors that should be shared between unit/integration tests
 * and the staging deployment server, such as:
 * - Using mock AI services (no GEMINI_API_KEY required)
 * - Verbose error logging
 * - Fallback URL handling
 *
 * Do NOT use this for security bypasses (auth, rate limiting) - those should
 * only be active in NODE_ENV=test, not staging.
 */
export const isTestLikeEnvironment = isTest || isStaging;

/**
 * Returns true if SMTP is configured (all required fields present).
 */
export const isSmtpConfigured =
  !!config.smtp.host && !!config.smtp.user && !!config.smtp.pass && !!config.smtp.fromEmail;

/**
 * Returns true if AI services are configured.
 */
export const isAiConfigured = !!config.ai.geminiApiKey;

/**
 * Returns true if Google Maps is configured.
 */
export const isGoogleMapsConfigured = !!config.google.mapsApiKey;

/**
 * Returns true if Sentry/Bugsink error tracking is configured and enabled.
 */
export const isSentryConfigured = !!config.sentry.dsn && config.sentry.enabled;

/**
 * Returns true if UPC Item DB API is configured.
 */
export const isUpcItemDbConfigured = !!config.upc.upcItemDbApiKey;

/**
 * Returns true if Barcode Lookup API is configured.
 */
export const isBarcodeLookupConfigured = !!config.upc.barcodeLookupApiKey;
