import { LogLevel, LoggerService, LogBufferRecord, LoggerOptions } from './logger.interface';

/**
 * @publicApi
 */
export class Logger implements LoggerService {
  protected context?: string;
  protected options: LoggerOptions;
  protected static logBuffer: LogBufferRecord[] = [];
  protected static staticInstanceRef?: LoggerService;
  protected static logLevels?: LogLevel[] = ['log', 'error', 'warn', 'debug', 'verbose', 'fatal'];
  private static isBufferAttached = false;
  protected localInstanceRef?: LoggerService;

  // Color schemes for different log levels
  private static readonly LOG_COLORS: { [key in LogLevel]: string } = {
    log: '#2196F3',      // Blue
    error: '#f44336',    // Red
    warn: '#ff9800',     // Orange
    debug: '#4caf50',    // Green
    verbose: '#9c27b0',  // Purple
    fatal: '#d32f2f'     // Dark Red
  };

  constructor(context?: string, options?: LoggerOptions) {
    this.context = context;
    this.options = {
      timestamp: true,
      environment: 'development',
      enableConsole: true,
      enableRemoteLogging: false,
      ...options
    };
    this.registerLocalInstanceRef();
  }

  get localInstance(): LoggerService {
    if (!this.localInstanceRef) {
      this.localInstanceRef = this;
    }
    return this.localInstanceRef;
  }

  /**
   * Write an 'error' level log.
   */
  error(message: any, stack?: string, context?: string): void;
  error(message: any, ...optionalParams: [...any, string?, string?]): void;
  error(message: any, ...args: any[]): void {
    const [stack, context] = this.extractStackAndContext(args);
    this.callFunction('error', message, stack, context || this.context);
  }

  /**
   * Write a 'log' level log.
   */
  log(message: any, context?: string): void;
  log(message: any, ...optionalParams: [...any, string?]): void;
  log(message: any, ...args: any[]): void {
    const context = args.length > 0 ? args[args.length - 1] : undefined;
    this.callFunction('log', message, undefined, context || this.context);
  }

  /**
   * Write a 'warn' level log.
   */
  warn(message: any, context?: string): void;
  warn(message: any, ...optionalParams: [...any, string?]): void;
  warn(message: any, ...args: any[]): void {
    const context = args.length > 0 ? args[args.length - 1] : undefined;
    this.callFunction('warn', message, undefined, context || this.context);
  }

  /**
   * Write a 'debug' level log.
   */
  debug(message: any, context?: string): void;
  debug(message: any, ...optionalParams: [...any, string?]): void;
  debug(message: any, ...args: any[]): void {
    const context = args.length > 0 ? args[args.length - 1] : undefined;
    this.callFunction('debug', message, undefined, context || this.context);
  }

  /**
   * Write a 'verbose' level log.
   */
  verbose(message: any, context?: string): void;
  verbose(message: any, ...optionalParams: [...any, string?]): void;
  verbose(message: any, ...args: any[]): void {
    const context = args.length > 0 ? args[args.length - 1] : undefined;
    this.callFunction('verbose', message, undefined, context || this.context);
  }

  /**
   * Write a 'fatal' level log.
   */
  fatal(message: any, context?: string): void;
  fatal(message: any, ...optionalParams: [...any, string?]): void;
  fatal(message: any, ...args: any[]): void {
    const context = args.length > 0 ? args[args.length - 1] : undefined;
    this.callFunction('fatal', message, undefined, context || this.context);
  }

  /**
   * Static methods
   */
  static error(message: any, stackOrContext?: string): void;
  static error(message: any, context?: string): void;
  static error(message: any, stack?: string, context?: string): void;
  static error(message: any, ...args: any[]): void {
    const instance = Logger.getStaticInstance();
    if (instance.error) {
      instance.error(message, ...args);
    }
  }

  static log(message: any, context?: string): void;
  static log(message: any, ...optionalParams: [...any, string?]): void;
  static log(message: any, ...args: any[]): void {
    const instance = Logger.getStaticInstance();
    if (instance.log) {
      instance.log(message, ...args);
    }
  }

  static warn(message: any, context?: string): void;
  static warn(message: any, ...optionalParams: [...any, string?]): void;
  static warn(message: any, ...args: any[]): void {
    const instance = Logger.getStaticInstance();
    if (instance.warn) {
      instance.warn(message, ...args);
    }
  }

  static debug(message: any, context?: string): void;
  static debug(message: any, ...optionalParams: [...any, string?]): void;
  static debug(message: any, ...args: any[]): void {
    const instance = Logger.getStaticInstance();
    if (instance.debug) {
      instance.debug(message, ...args);
    }
  }

  static verbose(message: any, context?: string): void;
  static verbose(message: any, ...optionalParams: [...any, string?]): void;
  static verbose(message: any, ...args: any[]): void {
    const instance = Logger.getStaticInstance();
    if (instance.verbose) {
      instance.verbose(message, ...args);
    }
  }

  static fatal(message: any, context?: string): void;
  static fatal(message: any, ...optionalParams: [...any, string?]): void;
  static fatal(message: any, ...args: any[]): void {
    const instance = Logger.getStaticInstance();
    if (instance.fatal) {
      instance.fatal(message, ...args);
    }
  }

  /**
   * Print buffered logs and detach buffer.
   */
  static flush(): void {
    Logger.logBuffer.forEach(record => {
      record.methodRef.apply(null, record.arguments);
    });
    Logger.logBuffer = [];
  }

  /**
   * Attach buffer.
   * Turns on initialization logs buffering.
   */
  static attachBuffer(): void {
    Logger.isBufferAttached = true;
  }

  /**
   * Detach buffer.
   * Turns off initialization logs buffering.
   */
  static detachBuffer(): void {
    Logger.isBufferAttached = false;
    Logger.flush();
  }

  static getTimestamp(): string {
    const now = new Date();
    return now.toISOString();
  }

  static overrideLogger(logger: LoggerService | LogLevel[] | boolean): any {
    if (Array.isArray(logger)) {
      Logger.logLevels = logger;
    } else if (typeof logger === 'boolean') {
      Logger.logLevels = logger ? ['log', 'error', 'warn', 'debug', 'verbose', 'fatal'] : [];
    } else {
      Logger.staticInstanceRef = logger;
    }
  }

  static isLevelEnabled(level: LogLevel): boolean {
    return Logger.logLevels ? Logger.logLevels.includes(level) : true;
  }

  setLogLevels(levels: LogLevel[]): void {
    Logger.logLevels = levels;
  }

  private registerLocalInstanceRef(): void {
    if (!Logger.staticInstanceRef) {
      Logger.staticInstanceRef = this;
    }
    this.localInstanceRef = this;
  }

  private static getStaticInstance(): LoggerService {
    if (!Logger.staticInstanceRef) {
      Logger.staticInstanceRef = new Logger();
    }
    return Logger.staticInstanceRef;
  }

  private extractStackAndContext(args: any[]): [string?, string?] {
    if (args.length === 0) return [undefined, undefined];
    if (args.length === 1) return [undefined, args[0]];
    return [args[0], args[1]];
  }

  private callFunction(
    level: LogLevel,
    message: any,
    stack?: string,
    context?: string
  ): void {
    if (!Logger.isLevelEnabled(level)) {
      return;
    }

    const logMethod = this.getLogMethod(level);
    const formattedMessage = this.formatMessage(level, message, stack, context);

    if (Logger.isBufferAttached) {
      Logger.logBuffer.push({
        methodRef: logMethod,
        arguments: formattedMessage.args,
        timestamp: new Date(),
        level,
        context
      });
    } else {
      if (this.options.enableConsole) {
        logMethod.apply(console, formattedMessage.args);
      }
    }
  }

  private getLogMethod(level: LogLevel): Function {
    switch (level) {
      case 'error':
      case 'fatal':
        return console.error;
      case 'warn':
        return console.warn;
      case 'debug':
        return console.debug || console.log;
      case 'verbose':
        return console.info || console.log;
      default:
        return console.log;
    }
  }

  private formatMessage(
    level: LogLevel,
    message: any,
    stack?: string,
    context?: string
  ): { args: any[] } {
    const timestamp = this.options.timestamp ? Logger.getTimestamp() : '';
    const contextStr = context ? `[${context}]` : '';
    const levelStr = `[${level.toUpperCase()}]`;
    
    // Browser-friendly formatting with colors
    if (typeof window !== 'undefined') {
      const color = Logger.LOG_COLORS[level];
      const prefix = `%c${timestamp} ${levelStr} ${contextStr}`.trim();
      const style = `color: ${color}; font-weight: bold;`;
      
      const args = [prefix, style, message];
      
      if (stack && level === 'error') {
        args.push('\nStack:', stack);
      }
      
      return { args };
    } else {
      // Node.js environment fallback
      const prefix = `${timestamp} ${levelStr} ${contextStr}`.trim();
      const args = [prefix, message];
      
      if (stack && level === 'error') {
        args.push('\nStack:', stack);
      }
      
      return { args };
    }
  }


} 