/**
 * Secret Validation Framework
 * 
 * Implements FIPS 140-2 Level 2+ secret validation per NIST SP 800-53 SC-12
 * and NIST SP 800-171 3.5.10 (Cryptographic Key Management)
 * 
 * This module ensures that:
 * - No default or insecure secrets are used in production
 * - Secrets meet minimum complexity requirements
 * - Secrets are properly validated before use
 */

import { logger } from './logger'

/**
 * Default/insecure secrets that must never be used in production
 */
const INSECURE_SECRETS = [
  'your-secret-key-change-in-production',
  'change-me',
  'secret',
  'password',
  'admin',
  'root',
  'postgres',
  'default',
  'test',
  'dev',
  'development',
  '123456',
  'password123',
  '',
]

/**
 * Minimum secret requirements per DoD/MilSpec standards
 */
interface SecretRequirements {
  minLength: number
  requireUppercase: boolean
  requireLowercase: boolean
  requireNumbers: boolean
  requireSpecialChars: boolean
  maxAge?: number // in days
}

const DEFAULT_REQUIREMENTS: SecretRequirements = {
  minLength: 32, // NIST SP 800-63B recommends minimum 32 characters for secrets
  requireUppercase: true,
  requireLowercase: true,
  requireNumbers: true,
  requireSpecialChars: true,
}

/**
 * Validates that a secret meets DoD/MilSpec requirements
 */
export class SecretValidationError extends Error {
  constructor(
    message: string,
    public code: string,
    public requirements?: SecretRequirements
  ) {
    super(message)
    this.name = 'SecretValidationError'
  }
}

/**
 * Validates a secret against security requirements
 */
export function validateSecret(
  secret: string | undefined,
  name: string,
  requirements: Partial<SecretRequirements> = {}
): void {
  const req = { ...DEFAULT_REQUIREMENTS, ...requirements }

  // Check if secret is provided
  if (!secret) {
    throw new SecretValidationError(
      `Secret '${name}' is required but not provided`,
      'MISSING_SECRET',
      req
    )
  }

  // Check for insecure defaults
  if (INSECURE_SECRETS.includes(secret.toLowerCase().trim())) {
    throw new SecretValidationError(
      `Secret '${name}' uses an insecure default value. This is not allowed in production.`,
      'INSECURE_DEFAULT',
      req
    )
  }

  // Check minimum length
  if (secret.length < req.minLength) {
    throw new SecretValidationError(
      `Secret '${name}' must be at least ${req.minLength} characters long (current: ${secret.length})`,
      'INSUFFICIENT_LENGTH',
      req
    )
  }

  // Check complexity requirements
  if (req.requireUppercase && !/[A-Z]/.test(secret)) {
    throw new SecretValidationError(
      `Secret '${name}' must contain at least one uppercase letter`,
      'MISSING_UPPERCASE',
      req
    )
  }

  if (req.requireLowercase && !/[a-z]/.test(secret)) {
    throw new SecretValidationError(
      `Secret '${name}' must contain at least one lowercase letter`,
      'MISSING_LOWERCASE',
      req
    )
  }

  if (req.requireNumbers && !/[0-9]/.test(secret)) {
    throw new SecretValidationError(
      `Secret '${name}' must contain at least one number`,
      'MISSING_NUMBER',
      req
    )
  }

  if (req.requireSpecialChars && !/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(secret)) {
    throw new SecretValidationError(
      `Secret '${name}' must contain at least one special character`,
      'MISSING_SPECIAL_CHAR',
      req
    )
  }

  // Check for common patterns (optional but recommended)
  if (isCommonPattern(secret)) {
    logger.warn(`Secret '${name}' matches a common pattern and may be insecure`)
  }
}

/**
 * Checks if a secret matches common insecure patterns
 */
function isCommonPattern(secret: string): boolean {
  const patterns = [
    /^[a-z]+$/i, // All same case
    /^[0-9]+$/, // All numbers
    /^(.)\1+$/, // All same character
    /^12345/, // Sequential numbers
    /^abcde/i, // Sequential letters
  ]

  return patterns.some(pattern => pattern.test(secret))
}

/**
 * Validates a secret and returns it, or throws if invalid
 * This is the main function to use for secret validation
 */
export function requireSecret(
  secret: string | undefined,
  name: string,
  requirements?: Partial<SecretRequirements>
): string {
  validateSecret(secret, name, requirements)
  return secret!
}

/**
 * Validates a secret in production environment
 * Fails fast if secret is insecure in production
 */
export function requireProductionSecret(
  secret: string | undefined,
  name: string,
  requirements?: Partial<SecretRequirements>
): string {
  const isProduction = process.env.NODE_ENV === 'production' || 
                      process.env.ENVIRONMENT === 'production' ||
                      process.env.PRODUCTION === 'true'

  if (isProduction) {
    // Stricter requirements for production
    const prodRequirements: SecretRequirements = {
      ...DEFAULT_REQUIREMENTS,
      minLength: 64, // Longer secrets for production
      ...requirements,
    }
    validateSecret(secret, name, prodRequirements)
  } else {
    validateSecret(secret, name, requirements)
  }

  return secret!
}

/**
 * Validates JWT secret specifically
 */
export function requireJWTSecret(): string {
  return requireProductionSecret(
    process.env.JWT_SECRET,
    'JWT_SECRET',
    {
      minLength: 64, // JWT secrets should be longer
    }
  )
}

/**
 * Validates database password specifically
 * Relaxed requirements for development mode
 */
export function requireDatabasePassword(): string {
  const isProduction = process.env.NODE_ENV === 'production' || 
                      process.env.ENVIRONMENT === 'production' ||
                      process.env.PRODUCTION === 'true'

  if (isProduction) {
    return requireProductionSecret(
      process.env.DB_PASSWORD,
      'DB_PASSWORD',
      {
        minLength: 32,
      }
    )
  } else {
    // Development mode: relaxed requirements
    // Still validate but allow shorter passwords for local development
    const password = process.env.DB_PASSWORD
    if (!password) {
      throw new SecretValidationError(
        'DB_PASSWORD is required but not provided. Please set it in your .env file.',
        'MISSING_SECRET',
        { minLength: 8, requireUppercase: false, requireLowercase: false, requireNumbers: false, requireSpecialChars: false }
      )
    }
    
    // Basic validation for dev (just check it's not empty and not insecure)
    if (password.length < 8) {
      throw new SecretValidationError(
        'DB_PASSWORD must be at least 8 characters long for development',
        'INSUFFICIENT_LENGTH',
        { minLength: 8 }
      )
    }
    
    if (INSECURE_SECRETS.includes(password.toLowerCase().trim())) {
      throw new SecretValidationError(
        'DB_PASSWORD uses an insecure default value',
        'INSECURE_DEFAULT'
      )
    }
    
    return password
  }
}

/**
 * Validates all required secrets at application startup
 * Call this during application initialization
 */
export function validateAllSecrets(): void {
  const isProduction = process.env.NODE_ENV === 'production' || 
                      process.env.ENVIRONMENT === 'production' ||
                      process.env.PRODUCTION === 'true'

  if (!isProduction) {
    logger.warn('Not in production environment - secret validation may be relaxed')
    return
  }

  logger.info('Validating all required secrets for production...')

  const requiredSecrets = [
    { env: 'JWT_SECRET', name: 'JWT_SECRET', minLength: 64 },
    { env: 'DB_PASSWORD', name: 'DB_PASSWORD', minLength: 32 },
    { env: 'KEYCLOAK_CLIENT_SECRET', name: 'KEYCLOAK_CLIENT_SECRET', minLength: 32 },
  ]

  const missing: string[] = []
  const invalid: Array<{ name: string; error: string }> = []

  for (const secret of requiredSecrets) {
    const value = process.env[secret.env]
    
    if (!value) {
      missing.push(secret.name)
      continue
    }

    try {
      requireProductionSecret(value, secret.name, { minLength: secret.minLength })
    } catch (error) {
      if (error instanceof SecretValidationError) {
        invalid.push({ name: secret.name, error: error.message })
      } else {
        invalid.push({ name: secret.name, error: String(error) })
      }
    }
  }

  if (missing.length > 0) {
    throw new Error(
      `Missing required secrets in production: ${missing.join(', ')}\n` +
      'Please set all required environment variables before starting the application.'
    )
  }

  if (invalid.length > 0) {
    const errors = invalid.map(i => `  - ${i.name}: ${i.error}`).join('\n')
    throw new Error(
      `Invalid secrets in production:\n${errors}\n` +
      'Please ensure all secrets meet security requirements.'
    )
  }

  logger.info('All required secrets validated successfully')
}

