import { z } from 'zod';

export interface ValidationResult {
  valid: boolean;
  errors?: Array<{
    path: string;
    message: string;
    expected?: string;
    received?: string;
  }>;
  warnings?: Array<{
    path: string;
    message: string;
  }>;
}

/**
 * Validate configuration against a schema
 */
export function validateConfig<T>(config: unknown, schema: z.ZodSchema<T>): ValidationResult {
  try {
    schema.parse(config);
    return { valid: true };
  } catch (error) {
    if (error instanceof z.ZodError) {
      const errors = error.errors.map(err => ({
        path: err.path.join('.'),
        message: err.message,
        expected: 'expected' in err ? String(err.expected) : undefined,
        received: 'received' in err ? String(err.received) : undefined,
      }));

      return { valid: false, errors };
    }

    throw error;
  }
}

/**
 * Check for deprecated configuration options
 */
export function checkDeprecations(
  config: Record<string, unknown>,
  deprecations: Record<string, string>
): ValidationResult['warnings'] {
  const warnings: ValidationResult['warnings'] = [];

  function checkObject(obj: Record<string, unknown>, path: string[] = []): void {
    for (const [key, value] of Object.entries(obj)) {
      const currentPath = [...path, key];
      const pathStr = currentPath.join('.');

      if (pathStr in deprecations) {
        const deprecationMessage = deprecations[pathStr];
        if (deprecationMessage) {
          warnings?.push({
            path: pathStr,
            message: deprecationMessage,
          });
        }
      }

      if (value && typeof value === 'object' && !Array.isArray(value)) {
        checkObject(value as Record<string, unknown>, currentPath);
      }
    }
  }

  checkObject(config);
  return warnings;
}

/**
 * Check for required environment variables
 */
export function checkRequiredEnvVars(required: string[]): ValidationResult {
  const errors: ValidationResult['errors'] = [];

  for (const envVar of required) {
    if (!process.env[envVar]) {
      errors.push({
        path: `env.${envVar}`,
        message: `Required environment variable ${envVar} is not set`,
      });
    }
  }

  return {
    valid: errors.length === 0,
    errors: errors.length > 0 ? errors : undefined,
  };
}

/**
 * Validate configuration completeness
 */
export function validateCompleteness(
  config: Record<string, any>,
  required: string[]
): ValidationResult {
  const errors: ValidationResult['errors'] = [];

  for (const path of required) {
    const keys = path.split('.');
    let current: any = config;
    let found = true;

    for (const key of keys) {
      if (current && typeof current === 'object' && key in current) {
        current = current[key];
      } else {
        found = false;
        break;
      }
    }

    if (!found || current === undefined || current === null) {
      errors.push({
        path,
        message: `Required configuration value is missing`,
      });
    }
  }

  return {
    valid: errors.length === 0,
    errors: errors.length > 0 ? errors : undefined,
  };
}

/**
 * Format validation result for display
 */
export function formatValidationResult(result: ValidationResult): string {
  const lines: string[] = [];

  if (result.valid) {
    lines.push('✅ Configuration is valid');
  } else {
    lines.push('❌ Configuration validation failed');
  }

  if (result.errors && result.errors.length > 0) {
    lines.push('\nErrors:');
    for (const error of result.errors) {
      lines.push(`  - ${error.path}: ${error.message}`);
      if (error.expected && error.received) {
        lines.push(`    Expected: ${error.expected}, Received: ${error.received}`);
      }
    }
  }

  if (result.warnings && result.warnings.length > 0) {
    lines.push('\nWarnings:');
    for (const warning of result.warnings) {
      lines.push(`  - ${warning.path}: ${warning.message}`);
    }
  }

  return lines.join('\n');
}

/**
 * Create a strict schema that doesn't allow extra properties
 */
export function createStrictSchema<T extends z.ZodRawShape>(shape: T): z.ZodObject<T, 'strict'> {
  return z.object(shape).strict();
}

/**
 * Merge multiple schemas
 */
export function mergeSchemas<T extends z.ZodSchema[]>(
  ...schemas: T
): z.ZodIntersection<T[0], T[1]> {
  if (schemas.length < 2) {
    throw new Error('At least two schemas required for merge');
  }

  let result = schemas[0]!.and(schemas[1]!);

  for (let i = 2; i < schemas.length; i++) {
    result = result.and(schemas[i]!) as any;
  }

  return result as any;
}
