/**
 * Custom Error Types for Sync Operations
 */

/**
 * Base sync error class
 */
export class SyncError extends Error {
  public readonly code: string;
  public readonly context?: Record<string, any>;
  public readonly isRetryable: boolean;

  constructor(
    message: string,
    code: string,
    context?: Record<string, any>,
    isRetryable: boolean = false
  ) {
    super(message);
    this.name = 'SyncError';
    this.code = code;
    this.context = context;
    this.isRetryable = isRetryable;
    
    // Maintains proper stack trace for where our error was thrown
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, this.constructor);
    }
  }
}

/**
 * Network-related errors (connection failures, timeouts)
 */
export class NetworkError extends SyncError {
  constructor(message: string, context?: Record<string, any>) {
    super(message, 'NETWORK_ERROR', context, true);
    this.name = 'NetworkError';
  }
}

/**
 * Authentication/Authorization errors
 */
export class AuthError extends SyncError {
  constructor(message: string, context?: Record<string, any>) {
    super(message, 'AUTH_ERROR', context, false);
    this.name = 'AuthError';
  }
}

/**
 * Rate limit errors
 */
export class RateLimitError extends SyncError {
  public readonly retryAfter?: number;

  constructor(message: string, retryAfter?: number, context?: Record<string, any>) {
    super(message, 'RATE_LIMIT_ERROR', context, true);
    this.name = 'RateLimitError';
    this.retryAfter = retryAfter;
  }
}

/**
 * API-related errors
 */
export class ApiError extends SyncError {
  public readonly statusCode?: number;

  constructor(message: string, statusCode?: number, context?: Record<string, any>) {
    super(message, 'API_ERROR', context, statusCode ? statusCode >= 500 : false);
    this.name = 'ApiError';
    this.statusCode = statusCode;
  }
}

/**
 * Database-related errors
 */
export class DatabaseError extends SyncError {
  constructor(message: string, context?: Record<string, any>, isRetryable: boolean = false) {
    super(message, 'DATABASE_ERROR', context, isRetryable);
    this.name = 'DatabaseError';
  }
}

/**
 * Database constraint violation errors
 */
export class ConstraintError extends SyncError {
  constructor(message: string, context?: Record<string, any>) {
    super(message, 'DATABASE_CONSTRAINT_ERROR', context, false);
    this.name = 'ConstraintError';
  }
}

/**
 * Data validation errors
 */
export class ValidationError extends SyncError {
  public readonly validationErrors: Array<{ field: string; message: string }>;

  constructor(
    message: string,
    validationErrors: Array<{ field: string; message: string }>,
    context?: Record<string, any>
  ) {
    super(message, 'VALIDATION_ERROR', context, false);
    this.name = 'ValidationError';
    this.validationErrors = validationErrors;
  }
}

/**
 * Data mapping errors
 */
export class MappingError extends SyncError {
  constructor(message: string, context?: Record<string, any>) {
    super(message, 'MAPPING_ERROR', context, false);
    this.name = 'MappingError';
  }
}

/**
 * Configuration errors
 */
export class ConfigError extends SyncError {
  constructor(message: string, context?: Record<string, any>) {
    super(message, 'CONFIG_ERROR', context, false);
    this.name = 'ConfigError';
  }
}

/**
 * Timeout errors
 */
export class TimeoutError extends SyncError {
  constructor(message: string, context?: Record<string, any>) {
    super(message, 'TIMEOUT_ERROR', context, true);
    this.name = 'TimeoutError';
  }
}

/**
 * Helper function to categorize generic errors
 */
export function categorizeError(error: any): SyncError {
  if (error instanceof SyncError) {
    return error;
  }

  const errorMessage = error instanceof Error ? error.message : String(error);
  const errorString = errorMessage.toLowerCase();

  // Network errors
  if (
    errorString.includes('econnrefused') ||
    errorString.includes('etimedout') ||
    errorString.includes('enotfound') ||
    errorString.includes('network')
  ) {
    return new NetworkError(errorMessage, { originalError: error });
  }

  // Auth errors
  if (
    errorString.includes('401') ||
    errorString.includes('403') ||
    errorString.includes('unauthorized') ||
    errorString.includes('forbidden')
  ) {
    return new AuthError(errorMessage, { originalError: error });
  }

  // Rate limit errors
  if (errorString.includes('429') || errorString.includes('rate limit')) {
    return new RateLimitError(errorMessage, undefined, { originalError: error });
  }

  // Database constraint errors
  if (
    errorString.includes('constraint') ||
    errorString.includes('duplicate') ||
    errorString.includes('unique violation')
  ) {
    return new ConstraintError(errorMessage, { originalError: error });
  }

  // Database errors
  if (
    errorString.includes('query') ||
    errorString.includes('sql') ||
    errorString.includes('database') ||
    errorString.includes('postgres')
  ) {
    return new DatabaseError(errorMessage, { originalError: error });
  }

  // Validation errors
  if (errorString.includes('validation') || errorString.includes('invalid')) {
    return new ValidationError(errorMessage, [], { originalError: error });
  }

  // Mapping errors
  if (errorString.includes('mapping') || errorString.includes('transform')) {
    return new MappingError(errorMessage, { originalError: error });
  }

  // Timeout errors
  if (errorString.includes('timeout') || errorString.includes('timed out')) {
    return new TimeoutError(errorMessage, { originalError: error });
  }

  // API errors (check for HTTP status codes)
  const statusMatch = errorString.match(/\b([45]\d{2})\b/);
  if (statusMatch) {
    const statusCode = parseInt(statusMatch[1]);
    return new ApiError(errorMessage, statusCode, { originalError: error });
  }

  // Default to generic sync error
  return new SyncError(errorMessage, 'UNKNOWN_ERROR', { originalError: error });
}

/**
 * Check if error is retryable
 */
export function isRetryableError(error: any): boolean {
  if (error instanceof SyncError) {
    return error.isRetryable;
  }

  const categorized = categorizeError(error);
  return categorized.isRetryable;
}

/**
 * Format error for logging
 */
export function formatErrorForLog(error: any): {
  message: string;
  code: string;
  stack?: string;
  context?: Record<string, any>;
  isRetryable: boolean;
} {
  if (error instanceof SyncError) {
    return {
      message: error.message,
      code: error.code,
      stack: error.stack,
      context: error.context,
      isRetryable: error.isRetryable,
    };
  }

  const categorized = categorizeError(error);
  return {
    message: categorized.message,
    code: categorized.code,
    stack: categorized.stack,
    context: categorized.context,
    isRetryable: categorized.isRetryable,
  };
}
