/**
 * Base Error Classes for Application-wide Error Handling
 */

export enum ErrorCode {
  // Generic errors
  UNKNOWN = 'UNKNOWN',
  VALIDATION_ERROR = 'VALIDATION_ERROR',
  NOT_FOUND = 'NOT_FOUND',
  UNAUTHORIZED = 'UNAUTHORIZED',
  FORBIDDEN = 'FORBIDDEN',
  
  // Service errors
  SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
  SERVICE_ERROR = 'SERVICE_ERROR',
  DEPENDENCY_ERROR = 'DEPENDENCY_ERROR',
  
  // Business logic errors
  INSUFFICIENT_BALANCE = 'INSUFFICIENT_BALANCE',
  INVALID_REWARD_TYPE = 'INVALID_REWARD_TYPE',
  REWARD_ALREADY_PROCESSED = 'REWARD_ALREADY_PROCESSED',
  PAYROLL_BATCH_LOCKED = 'PAYROLL_BATCH_LOCKED',
  WALLET_FROZEN = 'WALLET_FROZEN',
  
  // Data errors
  DATA_INTEGRITY_ERROR = 'DATA_INTEGRITY_ERROR',
  DUPLICATE_ENTRY = 'DUPLICATE_ENTRY',
  CONSTRAINT_VIOLATION = 'CONSTRAINT_VIOLATION',
  
  // External service errors
  EXTERNAL_API_ERROR = 'EXTERNAL_API_ERROR',
  NETWORK_ERROR = 'NETWORK_ERROR',
  TIMEOUT_ERROR = 'TIMEOUT_ERROR'
}

export interface ErrorContext {
  userId?: string;
  requestId?: string;
  timestamp?: Date;
  metadata?: Record<string, any>;
}

export abstract class BaseError extends Error {
  public readonly code: ErrorCode;
  public readonly context: ErrorContext;
  public readonly isOperational: boolean;
  public readonly statusCode: number;

  constructor(
    message: string,
    code: ErrorCode,
    statusCode: number = 500,
    context: ErrorContext = {},
    isOperational: boolean = true
  ) {
    super(message);
    
    this.name = this.constructor.name;
    this.code = code;
    this.statusCode = statusCode;
    this.context = {
      ...context,
      timestamp: context.timestamp || new Date()
    };
    this.isOperational = isOperational;

    // Maintains proper stack trace for where our error was thrown (only available on V8)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, this.constructor);
    }
  }

  public toJSON(): Record<string, any> {
    return {
      name: this.name,
      message: this.message,
      code: this.code,
      statusCode: this.statusCode,
      context: this.context,
      isOperational: this.isOperational,
      stack: this.stack
    };
  }
}

export class ValidationError extends BaseError {
  public readonly field?: string;
  public readonly value?: any;

  constructor(
    message: string,
    field?: string,
    value?: any,
    context: ErrorContext = {}
  ) {
    super(message, ErrorCode.VALIDATION_ERROR, 400, context);
    this.field = field;
    this.value = value;
  }
}

export class NotFoundError extends BaseError {
  public readonly resource: string;
  public readonly identifier?: string;

  constructor(
    resource: string,
    identifier?: string,
    context: ErrorContext = {}
  ) {
    const message = identifier 
      ? `${resource} with identifier '${identifier}' not found`
      : `${resource} not found`;
    
    super(message, ErrorCode.NOT_FOUND, 404, context);
    this.resource = resource;
    this.identifier = identifier;
  }
}

export class ServiceError extends BaseError {
  public readonly serviceName: string;
  public readonly operation?: string;

  constructor(
    serviceName: string,
    message: string,
    operation?: string,
    context: ErrorContext = {}
  ) {
    super(message, ErrorCode.SERVICE_ERROR, 500, context);
    this.serviceName = serviceName;
    this.operation = operation;
  }
}

export class BusinessLogicError extends BaseError {
  public readonly businessRule: string;

  constructor(
    message: string,
    businessRule: string,
    code: ErrorCode = ErrorCode.VALIDATION_ERROR,
    context: ErrorContext = {}
  ) {
    super(message, code, 422, context);
    this.businessRule = businessRule;
  }
}

export class ExternalServiceError extends BaseError {
  public readonly serviceName: string;
  public readonly originalError?: Error;

  constructor(
    serviceName: string,
    message: string,
    originalError?: Error,
    context: ErrorContext = {}
  ) {
    super(message, ErrorCode.EXTERNAL_API_ERROR, 502, context);
    this.serviceName = serviceName;
    this.originalError = originalError;
  }
}

// Specific business errors for the payroll reward system
export class InsufficientBalanceError extends BusinessLogicError {
  public readonly requestedAmount: number;
  public readonly availableBalance: number;

  constructor(
    requestedAmount: number,
    availableBalance: number,
    context: ErrorContext = {}
  ) {
    super(
      `Insufficient balance. Requested: ${requestedAmount}, Available: ${availableBalance}`,
      'insufficient_balance',
      ErrorCode.INSUFFICIENT_BALANCE,
      context
    );
    this.requestedAmount = requestedAmount;
    this.availableBalance = availableBalance;
  }
}

export class WalletFrozenError extends BusinessLogicError {
  public readonly walletId: string;

  constructor(walletId: string, context: ErrorContext = {}) {
    super(
      `Wallet ${walletId} is frozen and cannot perform transactions`,
      'wallet_frozen',
      ErrorCode.WALLET_FROZEN,
      context
    );
    this.walletId = walletId;
  }
}

export class RewardAlreadyProcessedError extends BusinessLogicError {
  public readonly rewardId: string;
  public readonly currentStatus: string;

  constructor(
    rewardId: string,
    currentStatus: string,
    context: ErrorContext = {}
  ) {
    super(
      `Reward ${rewardId} is already processed with status: ${currentStatus}`,
      'reward_already_processed',
      ErrorCode.REWARD_ALREADY_PROCESSED,
      context
    );
    this.rewardId = rewardId;
    this.currentStatus = currentStatus;
  }
}

export class PayrollBatchLockedError extends BusinessLogicError {
  public readonly batchId: string;
  public readonly lockReason: string;

  constructor(
    batchId: string,
    lockReason: string,
    context: ErrorContext = {}
  ) {
    super(
      `Payroll batch ${batchId} is locked: ${lockReason}`,
      'payroll_batch_locked',
      ErrorCode.PAYROLL_BATCH_LOCKED,
      context
    );
    this.batchId = batchId;
    this.lockReason = lockReason;
  }
}