/**
 * Simplified Pino-based logger for Stock Bot platform
 *
 * Features:
 * - High performance JSON logging with Pino
 * - Console, file, and Loki transports
 * - Structured logging with metadata
 * - Service-specific context
 */

import pino from 'pino';
import pretty from 'pino-pretty';
import type { LogContext, LoggerConfig, LogLevel, LogMetadata } from './types';

// Simple cache for logger instances
const loggerCache = new Map<string, pino.Logger>();

// Global config that can be set
let globalConfig: LoggerConfig = {
  logLevel: 'info', // Default to info, but trace and fatal are supported
  logConsole: true,
  logFile: false,
  logFilePath: './logs',
  logLoki: false,
  environment: 'development',
  hideObject: false,
};

// Log level priorities for comparison
const LOG_LEVELS: Record<LogLevel, number> = {
  trace: 10,
  debug: 20,
  info: 30,
  warn: 40,
  error: 50,
  fatal: 60,
};

/**
 * Set global logger configuration
 */
export function setLoggerConfig(config: LoggerConfig): void {
  globalConfig = { ...globalConfig, ...config };
  // Clear cache to force recreation with new config
  loggerCache.clear();
}
/**
 * Create logger destination using multistream approach:
 * - Console: In-process pretty stream (fast shutdown, disabled in production)
 * - File/Loki: Worker transports (default timeout, ok to wait)
 */
function createDestination(
  serviceName: string,
  config: LoggerConfig = globalConfig
): pino.DestinationStream | null {
  const streams: pino.StreamEntry[] = [];

  // Console: In-process pretty stream for dev (fast shutdown)
  if (config.logConsole && config.environment !== 'production') {
    const prettyStream = pretty({
      sync: true, // IMPORTANT: Make async to prevent blocking the event loop
      colorize: true,
      translateTime: 'yyyy-mm-dd HH:MM:ss.l',
      messageFormat: '[{service}{childName}] {msg}',
      singleLine: false, // This was causing logs to be on one line
      hideObject: false, // Hide metadata objects
      ignore: 'pid,hostname,service,environment,version,childName',
      errorLikeObjectKeys: ['err', 'error'],
      errorProps: 'message,stack,name,code',
    });
    streams.push({ stream: prettyStream });
  }

  // File: Worker transport (has timeout but acceptable)
  if (config.logFile) {
    streams.push(
      pino.transport({
        target: 'pino/file',
        level: config.logLevel || 'info',
        options: {
          destination: `${config.logFilePath}/${serviceName}.log`,
          mkdir: true,
        },
      })
    );
  }

  // Loki: Worker transport (has timeout but acceptable)
  if (config.logLoki && config.lokiHost) {
    streams.push(
      pino.transport({
        target: 'pino-loki',
        level: config.logLevel || 'info',
        options: {
          host: config.lokiHost,
          labels: {
            service: serviceName,
            environment: config.environment || 'development',
          },
          ignore: 'childName',
          ...(config.lokiUser && config.lokiPassword
            ? {
                basicAuth: {
                  username: config.lokiUser,
                  password: config.lokiPassword,
                },
              }
            : {}),
        },
      })
    );
  }

  return streams.length > 0 ? pino.multistream(streams) : null;
}

/**
 * Get or create pino logger
 */
function getPinoLogger(serviceName: string, config: LoggerConfig = globalConfig): pino.Logger {
  const cacheKey = `${serviceName}-${JSON.stringify(config)}`;
  if (!loggerCache.has(cacheKey)) {
    const destination = createDestination(serviceName, config);

    const loggerOptions: pino.LoggerOptions = {
      level: config.logLevel || 'info',
      base: {
        service: serviceName,
        environment: config.environment || 'development',
        version: '1.0.0',
      },
    };

    const logger = destination ? pino(loggerOptions, destination) : pino(loggerOptions);

    loggerCache.set(cacheKey, logger);
  }

  const logger = loggerCache.get(cacheKey);
  if (!logger) {
    throw new Error(`Expected logger ${cacheKey} to exist in cache`);
  }
  return logger;
}

/**
 * Simplified Logger class
 */
export class Logger {
  private pino: pino.Logger;
  private context: LogContext;
  private serviceName: string;
  private childName?: string;

  constructor(serviceName: string, context: LogContext = {}, config?: LoggerConfig) {
    this.pino = getPinoLogger(serviceName, config);
    this.context = context;
    this.serviceName = serviceName;
  }

  /**
   * Check if a log level should be output based on global config
   */
  private shouldLog(level: LogLevel): boolean {
    const currentLevel = globalConfig.logLevel || 'info';
    return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
  }

  /**
   * Core log method
   */
  private log(level: LogLevel, message: string | object, metadata?: LogMetadata): void {
    // Skip if level is below current threshold
    if (!this.shouldLog(level)) {
      return;
    }

    let data = { ...this.context, ...metadata };

    // Hide all metadata if hideObject is enabled, EXCEPT for error and fatal levels
    if (globalConfig.hideObject && level !== 'error' && level !== 'fatal') {
      data = {}; // Clear all metadata
    }

    if (typeof message === 'string') {
      (this.pino as any)[level](data, message);
    } else {
      if (globalConfig.hideObject && level !== 'error' && level !== 'fatal') {
        (this.pino as any)[level]({}, `Object logged (hidden)`);
      } else {
        (this.pino as any)[level]({ ...data, data: message }, 'Object logged');
      }
    }
  }

  // Simple log level methods
  trace(message: string | object, metadata?: LogMetadata): void {
    this.log('trace', message, metadata);
  }

  debug(message: string | object, metadata?: LogMetadata): void {
    this.log('debug', message, metadata);
  }

  info(message: string | object, metadata?: LogMetadata): void {
    this.log('info', message, metadata);
  }

  warn(message: string | object, metadata?: LogMetadata): void {
    this.log('warn', message, metadata);
  }

  error(message: string | object, metadata?: (LogMetadata & { error?: any }) | unknown): void {
    let data: any = {};

    // Handle metadata parameter normalization
    if (metadata instanceof Error) {
      // Direct Error object as metadata
      data = { error: metadata };
    } else if (metadata !== null && typeof metadata === 'object') {
      // Object metadata (including arrays, but not null)
      data = { ...metadata };
    } else if (metadata !== undefined) {
      // Primitive values (string, number, boolean, etc.)
      data = { metadata };
    }

    // Handle multiple error properties in metadata
    const errorKeys = ['error', 'err', 'primaryError', 'secondaryError'];
    errorKeys.forEach(key => {
      if (data[key]) {
        const normalizedKey = key === 'error' ? 'err' : `${key}_normalized`;
        data[normalizedKey] = this.normalizeError(data[key]);

        // Only delete the original 'error' key to maintain other error properties
        if (key === 'error') {
          delete data.error;
        }
      }
    });

    this.log('error', message, data);
  }

  fatal(message: string | object, metadata?: (LogMetadata & { error?: any }) | unknown): void {
    let data: any = {};

    // Handle metadata parameter normalization (same as error)
    if (metadata instanceof Error) {
      data = { error: metadata };
    } else if (metadata !== null && typeof metadata === 'object') {
      data = { ...metadata };
    } else if (metadata !== undefined) {
      data = { metadata };
    }

    // Normalize error objects in the data
    const errorKeys = ['error', 'err', 'primaryError', 'secondaryError'];
    errorKeys.forEach(key => {
      if (data[key]) {
        const normalizedKey = key === 'error' ? 'err' : `${key}_normalized`;
        data[normalizedKey] = this.normalizeError(data[key]);

        if (key === 'error') {
          delete data.error;
        }
      }
    });

    this.log('fatal', message, data);
  }

  /**
   * Normalize any error type to a structured format
   */
  private normalizeError(error: any): any {
    if (error instanceof Error) {
      return {
        name: error.name,
        message: error.message,
        stack: error.stack,
      };
    }

    if (error && typeof error === 'object') {
      // Handle error-like objects
      return {
        name: error.name || 'UnknownError',
        message: error.message || error.toString(),
        ...(error.stack && { stack: error.stack }),
        ...(error.code && { code: error.code }),
        ...(error.status && { status: error.status }),
      };
    }

    // Handle primitives (string, number, etc.)
    return {
      name: 'UnknownError',
      message: String(error),
    };
  }
  /**
   * Create child logger with additional context
   */
  child(serviceName: string, context?: LogContext): Logger {
    // Create child logger that shares the same pino instance with additional context
    const childLogger = Object.create(Logger.prototype);
    childLogger.serviceName = this.serviceName;
    childLogger.childName = serviceName;
    childLogger.context = { ...this.context, ...context };
    const childBindings = {
      service: this.serviceName,
      childName: ' -> ' + serviceName,
      ...(context || childLogger.context),
    };

    childLogger.pino = this.pino.child(childBindings);
    return childLogger;
    // }
    // childLogger.pino = this.pino.child(context || childLogger.context); // Let pino handle level inheritance naturally
    // return childLogger;
  }

  // Getters for service and context
  getServiceName(): string {
    return this.serviceName;
  }
  getChildName(): string | undefined {
    return this.childName;
  }
}

/**
 * Main factory function
 */
export function getLogger(
  serviceName: string,
  context?: LogContext,
  config?: LoggerConfig
): Logger {
  return new Logger(serviceName, context, config);
}

/**
 * Gracefully shutdown all logger instances
 * This ensures all transports are flushed and closed properly
 */
export async function shutdownLoggers(): Promise<void> {
  try {
    // Log final message before shutdown
    for (const logger of loggerCache.values()) {
      logger.info('Logger shutting down...');
    }

    const flushPromises = Array.from(loggerCache.values()).map(logger => logger.flush());

    await Promise.all(flushPromises);

    // Give transports time to finish writing
    // This is especially important for file and network transports
    await new Promise(resolve => setTimeout(resolve, 100));
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error('Logger shutdown failed:', error);
  } finally {
    loggerCache.clear();
  }
}

/**
 * Graceful shutdown - flush all logger transports quickly
 * Use this in your application shutdown handlers
 */
export async function gracefulShutdown(): Promise<void> {
  const flushPromises: Promise<void>[] = [];

  for (const logger of loggerCache.values()) {
    // Use pino v9's flush() method - this is much faster than the complex shutdown
    flushPromises.push(
      new Promise<void>((resolve, reject) => {
        logger.flush((err?: Error) => {
          if (err) {
            reject(err);
          } else {
            resolve();
          }
        });
      })
    );
  }

  try {
    await Promise.all(flushPromises);
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error('Logger graceful shutdown failed:', error);
  }
}

// Export types for convenience
export type { LogContext, LogLevel, LogMetadata } from './types';
