import { z } from 'zod';

/**
 * Secret value wrapper to prevent accidental logging
 */
export class SecretValue<T = string> {
  private readonly value: T;
  private readonly masked: string;

  constructor(value: T, mask: string = '***') {
    this.value = value;
    this.masked = mask;
  }

  /**
   * Get the actual secret value
   * @param reason - Required reason for accessing the secret
   */
  reveal(reason: string): T {
    if (!reason) {
      throw new Error('Reason required for revealing secret value');
    }
    return this.value;
  }

  /**
   * Get masked representation
   */
  toString(): string {
    return this.masked;
  }

  /**
   * Prevent JSON serialization of actual value
   */
  toJSON(): string {
    return this.masked;
  }

  /**
   * Check if value matches without revealing it
   */
  equals(other: T): boolean {
    return this.value === other;
  }

  /**
   * Transform the secret value
   */
  map<R>(fn: (value: T) => R, reason: string): SecretValue<R> {
    return new SecretValue(fn(this.reveal(reason)));
  }
}

/**
 * Zod schema for secret values
 */
export const secretSchema = <T extends z.ZodTypeAny>(_schema: T) => {
  return z.custom<SecretValue<z.infer<T>>>(val => val instanceof SecretValue, {
    message: 'Expected SecretValue instance',
  });
};

/**
 * Transform string to SecretValue in Zod schema
 */
export const secretStringSchema = z.string().transform(val => new SecretValue(val));

/**
 * Create a secret value
 */
export function secret<T = string>(value: T, mask?: string): SecretValue<T> {
  return new SecretValue(value, mask);
}

/**
 * Check if a value is a secret
 */
export function isSecret(value: unknown): value is SecretValue {
  return value instanceof SecretValue;
}

/**
 * Redact secrets from an object
 */
export function redactSecrets<T extends Record<string, any>>(
  obj: T,
  secretPaths: string[] = []
): T {
  const result = { ...obj };

  // Redact known secret paths
  for (const path of secretPaths) {
    const keys = path.split('.');
    let current: any = result;

    for (let i = 0; i < keys.length - 1; i++) {
      const key = keys[i];
      if (key && current[key] && typeof current[key] === 'object') {
        current = current[key];
      } else {
        break;
      }
    }

    const lastKey = keys[keys.length - 1];
    if (current && lastKey && lastKey in current) {
      current[lastKey] = '***REDACTED***';
    }
  }

  // Recursively redact SecretValue instances
  function redactSecretValues(obj: any): any {
    if (obj === null || obj === undefined) {
      return obj;
    }

    if (isSecret(obj)) {
      return obj.toString();
    }

    if (Array.isArray(obj)) {
      return obj.map(redactSecretValues);
    }

    if (typeof obj === 'object') {
      const result: any = {};
      for (const [key, value] of Object.entries(obj)) {
        result[key] = redactSecretValues(value);
      }
      return result;
    }

    return obj;
  }

  return redactSecretValues(result);
}

/**
 * Environment variable names that should be treated as secrets
 */
export const COMMON_SECRET_PATTERNS = [
  /password/i,
  /secret/i,
  /key/i,
  /token/i,
  /credential/i,
  /private/i,
  /auth/i,
  /api[-_]?key/i,
];

/**
 * Check if an environment variable name indicates a secret
 */
export function isSecretEnvVar(name: string): boolean {
  return COMMON_SECRET_PATTERNS.some(pattern => pattern.test(name));
}

/**
 * Wrap environment variables that look like secrets
 */
export function wrapSecretEnvVars(
  env: Record<string, string | undefined>
): Record<string, string | SecretValue | undefined> {
  const result: Record<string, string | SecretValue | undefined> = {};

  for (const [key, value] of Object.entries(env)) {
    if (value !== undefined && isSecretEnvVar(key)) {
      result[key] = new SecretValue(value, `***${key}***`);
    } else {
      result[key] = value;
    }
  }

  return result;
}
