/**
 * Comprehensive Error Handling for BaoLife
 * Provides custom exception classes, error decorators, and client error messaging.
 * Ported from Python errors/error_handler.py
 */

// ============================================================================
// Error Codes
// ============================================================================

export const ErrorCodes = {
  // General errors
  GAME_ERROR: 'GAME_ERROR',
  SERVER_ERROR: 'SERVER_ERROR',
  UNEXPECTED_ERROR: 'UNEXPECTED_ERROR',
  INVALID_ACTION: 'INVALID_ACTION',
  INVALID_REQUEST: 'INVALID_REQUEST',

  // Resource errors
  INSUFFICIENT_RESOURCES: 'INSUFFICIENT_RESOURCES',
  INSUFFICIENT_ENERGY: 'INSUFFICIENT_ENERGY',
  INSUFFICIENT_MONEY: 'INSUFFICIENT_MONEY',
  INSUFFICIENT_DIAMONDS: 'INSUFFICIENT_DIAMONDS',

  // Auth errors
  NOT_AUTHENTICATED: 'NOT_AUTHENTICATED',
  SESSION_EXPIRED: 'SESSION_EXPIRED',
  PLAYER_NOT_FOUND: 'PLAYER_NOT_FOUND',

  // Rate limiting
  RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
  DUPLICATE_TRANSACTION: 'DUPLICATE_TRANSACTION',

  // Validation errors
  VALIDATION_FAILED: 'VALIDATION_FAILED',
  INVALID_PRODUCT: 'INVALID_PRODUCT',
  PURCHASE_FAILED: 'PURCHASE_FAILED',

  // Connection errors
  CONNECTION_ERROR: 'CONNECTION_ERROR',
  WEBSOCKET_ERROR: 'WEBSOCKET_ERROR',
  DATABASE_ERROR: 'DATABASE_ERROR',
} as const;

export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];

// ============================================================================
// Base Error Classes
// ============================================================================

/**
 * Base exception class for all game-related errors
 */
export class GameError extends Error {
  public readonly errorCode: ErrorCode;
  public readonly retryPossible: boolean;
  public readonly context?: Record<string, unknown>;

  constructor(
    message: string,
    errorCode: ErrorCode = ErrorCodes.GAME_ERROR,
    retryPossible = false,
    context?: Record<string, unknown>
  ) {
    super(message);
    this.name = 'GameError';
    this.errorCode = errorCode;
    this.retryPossible = retryPossible;
    this.context = context;

    // Maintains proper stack trace for where our error was thrown
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, GameError);
    }
  }

  toJSON(): Record<string, unknown> {
    return {
      type: 'error',
      errorCode: this.errorCode,
      message: this.message,
      retryPossible: this.retryPossible,
      timestamp: new Date().toISOString(),
    };
  }
}

/**
 * Raised when player lacks required resources
 */
export class InsufficientResourcesError extends GameError {
  public readonly resourceType: string;
  public readonly required: number;
  public readonly current: number;

  constructor(resourceType: string, required: number, current: number) {
    const message = `Insufficient ${resourceType}: need ${required}, have ${current}`;
    super(message, ErrorCodes.INSUFFICIENT_RESOURCES, true, {
      resourceType,
      required,
      current,
    });
    this.name = 'InsufficientResourcesError';
    this.resourceType = resourceType;
    this.required = required;
    this.current = current;
  }
}

/**
 * Raised for internal server errors
 */
export class ServerError extends GameError {
  public readonly originalError?: Error;

  constructor(message: string, originalError?: Error) {
    super(message, ErrorCodes.SERVER_ERROR, true);
    this.name = 'ServerError';
    this.originalError = originalError;
  }
}

/**
 * Raised when a player is not found
 */
export class PlayerNotFoundError extends GameError {
  public readonly playerId: string;

  constructor(playerId: string) {
    super(`Player not found: ${playerId}`, ErrorCodes.PLAYER_NOT_FOUND, false);
    this.name = 'PlayerNotFoundError';
    this.playerId = playerId;
  }
}

/**
 * Raised when an action is invalid
 */
export class InvalidActionError extends GameError {
  public readonly action: string;

  constructor(action: string, message?: string) {
    super(
      message || `Invalid action: ${action}`,
      ErrorCodes.INVALID_ACTION,
      false
    );
    this.name = 'InvalidActionError';
    this.action = action;
  }
}

/**
 * Raised for validation failures
 */
export class ValidationError extends GameError {
  public readonly field?: string;

  constructor(message: string, field?: string) {
    super(message, ErrorCodes.VALIDATION_FAILED, false, { field });
    this.name = 'ValidationError';
    this.field = field;
  }
}

/**
 * Raised when rate limit is exceeded
 */
export class RateLimitError extends GameError {
  public readonly retryAfter?: number;

  constructor(message = 'Too many requests. Please wait and try again.', retryAfter?: number) {
    super(message, ErrorCodes.RATE_LIMIT_EXCEEDED, true, { retryAfter });
    this.name = 'RateLimitError';
    this.retryAfter = retryAfter;
  }
}

/**
 * Raised for database errors
 */
export class DatabaseError extends GameError {
  public readonly operation?: string;

  constructor(message: string, operation?: string, originalError?: Error) {
    super(message, ErrorCodes.DATABASE_ERROR, true, { operation });
    this.name = 'DatabaseError';
    this.operation = operation;
    if (originalError) {
      this.cause = originalError;
    }
  }
}

// ============================================================================
// Error Handler Types
// ============================================================================

export type SendToClientFn = (
  playerId: string,
  message: Record<string, unknown>
) => void | Promise<void>;

export interface ErrorHandlerOptions {
  sendToClient?: SendToClientFn;
  logLevel?: 'error' | 'warn' | 'info' | 'debug';
  rethrow?: boolean;
}

export interface ErrorPayload {
  type: 'error';
  errorCode: ErrorCode;
  message: string;
  retryPossible: boolean;
  timestamp: string;
  [key: string]: unknown;
}

// ============================================================================
// Error Handler Utilities
// ============================================================================

/**
 * Format error into a client-friendly payload
 */
export function formatErrorForClient(error: GameError): ErrorPayload {
  return {
    type: 'error',
    errorCode: error.errorCode,
    message: error.message,
    retryPossible: error.retryPossible,
    timestamp: new Date().toISOString(),
  };
}

/**
 * Format an unknown error into a client-friendly payload
 */
export function formatUnknownErrorForClient(error: unknown): ErrorPayload {
  if (error instanceof GameError) {
    return formatErrorForClient(error);
  }

  const message =
    error instanceof Error
      ? error.message
      : 'An unexpected error occurred. Please try again.';

  return {
    type: 'error',
    errorCode: ErrorCodes.UNEXPECTED_ERROR,
    message,
    retryPossible: true,
    timestamp: new Date().toISOString(),
  };
}

/**
 * Log an error with context
 */
export function logError(
  error: unknown,
  context?: {
    playerId?: string;
    operation?: string;
    additionalInfo?: Record<string, unknown>;
  }
): void {
  const errorInfo: Record<string, unknown> = {
    timestamp: new Date().toISOString(),
    ...context,
  };

  if (error instanceof GameError) {
    errorInfo.errorCode = error.errorCode;
    errorInfo.message = error.message;
    errorInfo.retryPossible = error.retryPossible;
    errorInfo.context = error.context;
    console.error('[GameError]', errorInfo);
  } else if (error instanceof Error) {
    errorInfo.message = error.message;
    errorInfo.stack = error.stack;
    console.error('[Error]', errorInfo);
  } else {
    errorInfo.error = error;
    console.error('[UnknownError]', errorInfo);
  }
}

/**
 * Send error to client
 */
export async function sendErrorToClient(
  playerId: string,
  error: GameError | Error | unknown,
  sendToClient: SendToClientFn
): Promise<void> {
  const payload = formatUnknownErrorForClient(error);

  try {
    await sendToClient(playerId, payload);
  } catch (sendError) {
    console.error(`Failed to send error to client ${playerId}:`, sendError);
  }
}

// ============================================================================
// Error Handler Decorator
// ============================================================================

/**
 * Decorator for automatic error handling with logging and client notification.
 *
 * Usage:
 * ```typescript
 * const safeHandler = handleErrors(async (playerId: string, amount: number) => {
 *   // ... code that might throw
 * }, { sendToClient: myClientSendFn });
 * ```
 */
export function handleErrors<T extends (...args: unknown[]) => Promise<unknown>>(
  fn: T,
  options: ErrorHandlerOptions = {}
): (...args: Parameters<T>) => Promise<ReturnType<T> | undefined> {
  const { sendToClient, logLevel = 'error', rethrow = false } = options;

  return async (...args: Parameters<T>): Promise<ReturnType<T> | undefined> => {
    // Try to extract playerId from first argument
    const playerId = typeof args[0] === 'string' ? args[0] : undefined;

    try {
      return (await fn(...args)) as ReturnType<T>;
    } catch (error) {
      // Log the error
      logError(error, {
        playerId,
        operation: fn.name || 'anonymous',
      });

      // Send to client if function provided
      if (sendToClient && playerId) {
        await sendErrorToClient(playerId, error, sendToClient);
      }

      // Rethrow if requested
      if (rethrow) {
        if (error instanceof GameError) {
          throw error;
        }
        throw new ServerError(
          'Unexpected error occurred',
          error instanceof Error ? error : undefined
        );
      }

      return undefined;
    }
  };
}

/**
 * Wrap a sync function with error handling
 */
export function handleErrorsSync<T extends (...args: unknown[]) => unknown>(
  fn: T,
  options: Omit<ErrorHandlerOptions, 'sendToClient'> = {}
): (...args: Parameters<T>) => ReturnType<T> | undefined {
  const { logLevel = 'error', rethrow = false } = options;

  return (...args: Parameters<T>): ReturnType<T> | undefined => {
    const playerId = typeof args[0] === 'string' ? args[0] : undefined;

    try {
      return fn(...args) as ReturnType<T>;
    } catch (error) {
      logError(error, {
        playerId,
        operation: fn.name || 'anonymous',
      });

      if (rethrow) {
        if (error instanceof GameError) {
          throw error;
        }
        throw new ServerError(
          'Unexpected error occurred',
          error instanceof Error ? error : undefined
        );
      }

      return undefined;
    }
  };
}

// ============================================================================
// Helper Functions
// ============================================================================

/**
 * Check if an error is a specific type
 */
export function isGameError(error: unknown): error is GameError {
  return error instanceof GameError;
}

export function isInsufficientResourcesError(
  error: unknown
): error is InsufficientResourcesError {
  return error instanceof InsufficientResourcesError;
}

export function isServerError(error: unknown): error is ServerError {
  return error instanceof ServerError;
}

export function isRateLimitError(error: unknown): error is RateLimitError {
  return error instanceof RateLimitError;
}

/**
 * Create a simple error response object
 */
export function createErrorResponse(
  errorCode: ErrorCode,
  message: string,
  retryPossible = false
): ErrorPayload {
  return {
    type: 'error',
    errorCode,
    message,
    retryPossible,
    timestamp: new Date().toISOString(),
  };
}

// ============================================================================
// Export
// ============================================================================

export const errorHandler = {
  // Error classes
  GameError,
  InsufficientResourcesError,
  ServerError,
  PlayerNotFoundError,
  InvalidActionError,
  ValidationError,
  RateLimitError,
  DatabaseError,
  // Error codes
  ErrorCodes,
  // Utilities
  formatErrorForClient,
  formatUnknownErrorForClient,
  logError,
  sendErrorToClient,
  handleErrors,
  handleErrorsSync,
  // Type guards
  isGameError,
  isInsufficientResourcesError,
  isServerError,
  isRateLimitError,
  // Helpers
  createErrorResponse,
};
