/**
 * Enterprise Error System
 * 
 * Comprehensive error handling with:
 * - Type-safe error hierarchy
 * - Error codes for frontend mapping
 * - HTTP status code mapping
 * - Structured logging support
 */

/**
 * Error codes for frontend mapping and i18n
 */
export enum ErrorCode {
  // Generic errors
  INTERNAL_ERROR = 'INTERNAL_ERROR',
  VALIDATION_ERROR = 'VALIDATION_ERROR',
  NOT_FOUND = 'NOT_FOUND',
  UNAUTHORIZED = 'UNAUTHORIZED',
  FORBIDDEN = 'FORBIDDEN',
  CONFLICT = 'CONFLICT',
  BAD_REQUEST = 'BAD_REQUEST',
  
  // Authentication errors
  AUTH_INVALID_CREDENTIALS = 'AUTH_INVALID_CREDENTIALS',
  AUTH_TOKEN_EXPIRED = 'AUTH_TOKEN_EXPIRED',
  AUTH_TOKEN_INVALID = 'AUTH_TOKEN_INVALID',
  AUTH_TOKEN_BLACKLISTED = 'AUTH_TOKEN_BLACKLISTED',
  AUTH_REFRESH_EXPIRED = 'AUTH_REFRESH_EXPIRED',
  AUTH_ACCOUNT_INACTIVE = 'AUTH_ACCOUNT_INACTIVE',
  AUTH_SESSION_EXPIRED = 'AUTH_SESSION_EXPIRED',
  
  // Authorization errors
  PERMISSION_DENIED = 'PERMISSION_DENIED',
  INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS',
  RESOURCE_ACCESS_DENIED = 'RESOURCE_ACCESS_DENIED',
  
  // Rate limiting
  RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
  RATE_LIMIT_RETRY_AFTER = 'RATE_LIMIT_RETRY_AFTER',
  
  // Resource errors
  RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND',
  RESOURCE_ALREADY_EXISTS = 'RESOURCE_ALREADY_EXISTS',
  RESOURCE_DELETED = 'RESOURCE_DELETED',
  RESOURCE_LOCKED = 'RESOURCE_LOCKED',
  
  // Database errors
  DB_CONNECTION_ERROR = 'DB_CONNECTION_ERROR',
  DB_TRANSACTION_ERROR = 'DB_TRANSACTION_ERROR',
  DB_CONSTRAINT_VIOLATION = 'DB_CONSTRAINT_VIOLATION',
  DB_TIMEOUT = 'DB_TIMEOUT',
  
  // External service errors
  AI_SERVICE_ERROR = 'AI_SERVICE_ERROR',
  AI_RATE_LIMIT = 'AI_RATE_LIMIT',
  AI_TIMEOUT = 'AI_TIMEOUT',
  TELEGRAM_ERROR = 'TELEGRAM_ERROR',
  CACHE_ERROR = 'CACHE_ERROR',
  
  // Business logic errors
  EXERCISE_NOT_AVAILABLE = 'EXERCISE_NOT_AVAILABLE',
  EXERCISE_ALREADY_COMPLETED = 'EXERCISE_ALREADY_COMPLETED',
  INVALID_ANSWER_FORMAT = 'INVALID_ANSWER_FORMAT',
  MODULE_LOCKED = 'MODULE_LOCKED',
  PREREQUISITES_NOT_MET = 'PREREQUISITES_NOT_MET',
  
  // File upload errors
  FILE_TOO_LARGE = 'FILE_TOO_LARGE',
  FILE_INVALID_TYPE = 'FILE_INVALID_TYPE',
  FILE_UPLOAD_FAILED = 'FILE_UPLOAD_FAILED',
  FILE_NOT_FOUND = 'FILE_NOT_FOUND',
}

/**
 * Base application error
 */
export class AppError extends Error {
  public readonly code: ErrorCode;
  public readonly statusCode: number;
  public readonly isOperational: boolean;
  public readonly metadata: Record<string, unknown>;
  public readonly timestamp: Date;
  public readonly correlationId: string | undefined;

  constructor(
    message: string,
    code: ErrorCode = ErrorCode.INTERNAL_ERROR,
    statusCode: number = 500,
    isOperational: boolean = true,
    metadata: Record<string, unknown> = {},
    correlationId: string | undefined = undefined
  ) {
    super(message);
    
    this.name = this.constructor.name;
    this.code = code;
    this.statusCode = statusCode;
    this.isOperational = isOperational;
    this.metadata = metadata;
    this.timestamp = new Date();
    this.correlationId = correlationId;
    
    // Maintains proper stack trace for where error was thrown
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, this.constructor);
    }
  }

  /**
   * Serialize error for API response
   */
  toJSON() {
    const error: Record<string, unknown> = {
      code: this.code,
      message: this.message,
      timestamp: this.timestamp.toISOString(),
    };

    if (this.correlationId !== undefined) {
      error.correlationId = this.correlationId;
    }

    if (process.env.NODE_ENV === 'development') {
      error.stack = this.stack;
      error.metadata = this.metadata;
    }

    return {
      success: false,
      error,
    };
  }

  /**
   * Loggable error data
   */
  toLog() {
    return {
      error: this.message,
      code: this.code,
      statusCode: this.statusCode,
      stack: this.stack,
      ...this.metadata,
    };
  }
}

/**
 * 400 - Bad Request
 */
export class ValidationError extends AppError {
  constructor(
    message: string,
    metadata: Record<string, unknown> = {},
    correlationId: string | undefined = undefined
  ) {
    super(message, ErrorCode.VALIDATION_ERROR, 400, true, metadata, correlationId);
  }
}

/**
 * 401 - Unauthorized
 */
export class AuthenticationError extends AppError {
  constructor(
    message: string = 'Authentication required',
    code: ErrorCode = ErrorCode.UNAUTHORIZED,
    metadata: Record<string, unknown> = {},
    correlationId: string | undefined = undefined
  ) {
    super(message, code, 401, true, metadata, correlationId);
  }
}

/**
 * 403 - Forbidden
 */
export class AuthorizationError extends AppError {
  constructor(
    message: string = 'Permission denied',
    metadata: Record<string, unknown> = {},
    correlationId: string | undefined = undefined
  ) {
    super(message, ErrorCode.FORBIDDEN, 403, true, metadata, correlationId);
  }
}

/**
 * 404 - Not Found
 */
export class NotFoundError extends AppError {
  constructor(
    resource: string,
    metadata: Record<string, unknown> = {},
    correlationId: string | undefined = undefined
  ) {
    super(
      `${resource} not found`,
      ErrorCode.NOT_FOUND,
      404,
      true,
      metadata,
      correlationId
    );
  }
}

/**
 * 409 - Conflict
 */
export class ConflictError extends AppError {
  constructor(
    message: string,
    metadata: Record<string, unknown> = {},
    correlationId: string | undefined = undefined
  ) {
    super(message, ErrorCode.CONFLICT, 409, true, metadata, correlationId);
  }
}

/**
 * 429 - Too Many Requests
 */
export class RateLimitError extends AppError {
  public readonly retryAfter: number;

  constructor(
    retryAfter: number,
    message: string = 'Rate limit exceeded',
    metadata: Record<string, unknown> = {},
    correlationId: string | undefined = undefined
  ) {
    super(message, ErrorCode.RATE_LIMIT_EXCEEDED, 429, true, metadata, correlationId);
    this.retryAfter = retryAfter;
  }

  override toJSON() {
    return {
      ...super.toJSON(),
      retryAfter: this.retryAfter,
    };
  }
}

/**
 * 503 - Service Unavailable
 */
export class ServiceUnavailableError extends AppError {
  constructor(
    service: string,
    metadata: Record<string, unknown> = {},
    correlationId: string | undefined = undefined
  ) {
    super(
      `${service} is currently unavailable`,
      ErrorCode.INTERNAL_ERROR,
      503,
      true,
      metadata,
      correlationId
    );
  }
}

/**
 * Database errors
 */
export class DatabaseError extends AppError {
  constructor(
    message: string = 'Database operation failed',
    code: ErrorCode = ErrorCode.DB_TRANSACTION_ERROR,
    metadata: Record<string, unknown> = {},
    correlationId: string | undefined = undefined
  ) {
    super(message, code, 500, false, metadata, correlationId);
  }
}

/**
 * External service errors
 */
export class ExternalServiceError extends AppError {
  constructor(
    service: string,
    message: string,
    metadata: Record<string, unknown> = {},
    correlationId: string | undefined = undefined
  ) {
    super(
      `${service}: ${message}`,
      ErrorCode.INTERNAL_ERROR,
      502,
      true,
      metadata,
      correlationId
    );
  }
}

/**
 * Check if error is operational (expected)
 */
export function isOperationalError(error: Error): boolean {
  return error instanceof AppError && error.isOperational;
}

/**
 * Check if error is a Prisma error
 */
export function isPrismaError(error: Error): boolean {
  return error.name?.includes('Prisma') || 
    (error.message || '').includes('prisma') ||
    (error.message || '').includes('P2025') ||
    (error.message || '').includes('P2002');
}

/**
 * Map Prisma errors to AppError
 */
export function mapPrismaError(error: Error, correlationId: string | undefined = undefined): AppError {
  const message = error.message || 'Unknown database error';
  
  if (message.includes('P2025')) {
    return new NotFoundError('Resource', { originalError: message }, correlationId);
  }
  
  if (message.includes('P2002')) {
    const field = message.match(/unique constraint.*\`(\w+)\`/)?.[1] || 'field';
    return new ConflictError(
      `A record with this ${field} already exists`,
      { originalError: message },
      correlationId
    );
  }
  
  if (message.includes('P2003')) {
    return new ValidationError(
      'Foreign key constraint failed',
      { originalError: message },
      correlationId
    );
  }
  
  return new DatabaseError(
    'Database operation failed',
    ErrorCode.DB_TRANSACTION_ERROR,
    { originalError: message },
    correlationId
  );
}
