import { BaseError } from '../errors/index.js';

export enum LogLevel {
  DEBUG = 'DEBUG',
  INFO = 'INFO',
  WARN = 'WARN',
  ERROR = 'ERROR',
  NONE = 'NONE',
}

export type LoggerConfig = {
  level: LogLevel;
  customLogger?: CustomLogger;
};

export interface CustomLogger {
  debug(message: string, ...args: unknown[]): void;
  info(message: string, ...args: unknown[]): void;
  warn(message: string, ...args: unknown[]): void;
  error(message: string, ...args: unknown[]): void;
}

class DefaultLogger implements CustomLogger {
  debug(message: string, ...args: unknown[]): void {
    console.debug(`[${new Date().toISOString()} DEBUG] ${message}`, ...args);
  }

  info(message: string, ...args: unknown[]): void {
    console.info(`[${new Date().toISOString()} INFO] ${message}`, ...args);
  }

  warn(message: string, ...args: unknown[]): void {
    console.warn(`[${new Date().toISOString()} WARN] ${message}`, ...args);
  }

  error(message: string, ...args: unknown[]): void {
    console.error(`[${new Date().toISOString()} ERROR] ${message}`, ...args);
  }
}

class Logger {
  private static instance: Logger;
  private loggerImpl: CustomLogger = new DefaultLogger();
  private logLevel: LogLevel = LogLevel.INFO;

  private constructor() {}

  public static getInstance(): Logger {
    if (!Logger.instance) {
      Logger.instance = new Logger();
    }
    return Logger.instance;
  }

  public configure(config: LoggerConfig): void {
    this.logLevel = config.level;
    if (config.customLogger) {
      this.loggerImpl = config.customLogger;
    }
  }

  public debug(message: string, ...args: unknown[]): void {
    if (this.shouldLog(LogLevel.DEBUG)) {
      this.loggerImpl.debug(message, ...args);
    }
  }

  public info(message: string, ...args: unknown[]): void {
    if (this.shouldLog(LogLevel.INFO)) {
      this.loggerImpl.info(message, ...args);
    }
  }

  public warn(message: string, ...args: unknown[]): void {
    if (this.shouldLog(LogLevel.WARN)) {
      this.loggerImpl.warn(message, ...args);
    }
  }

  public error(message: string, error?: unknown): void {
    if (this.shouldLog(LogLevel.ERROR)) {
      const formattedError = this.formatError(error);
      this.loggerImpl.error(message, formattedError);
    }
  }

  public logError(error: unknown): void {
    if (this.shouldLog(LogLevel.ERROR)) {
      const formattedError = this.formatError(error);

      if (error instanceof BaseError) {
        this.loggerImpl.error(`${error.name}: ${error.message}`, formattedError);
      } else if (error instanceof Error) {
        this.loggerImpl.error(`${error.name}: ${error.message}`, formattedError);
      } else {
        this.loggerImpl.error('Unknown error', formattedError);
      }
    }
  }

  private shouldLog(level: LogLevel): boolean {
    const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];
    const currentLevelIndex = levels.indexOf(this.logLevel);
    const targetLevelIndex = levels.indexOf(level);

    return currentLevelIndex <= targetLevelIndex && this.logLevel !== LogLevel.NONE;
  }

  private formatError(error?: unknown): unknown {
    if (!error) return 'No error details provided';

    if (error instanceof BaseError) {
      return error.toJSON();
    }

    if (error instanceof Error) {
      return {
        name: error.name,
        message: error.message,
        stack: error.stack,
      };
    }

    return error;
  }
}

export const logger = Logger.getInstance();
