// src/server/shared/lib/logger.ts
// Dynamic log management system with channel-based level control

import pino from 'pino';
import pinoPretty from 'pino-pretty';
import fs from 'fs';
import path from 'path';
import type { LogChannel, LogLevel, LogConfig } from '@shared/contracts/logger';

/**
 * Path to the log configuration file
 */
const CONFIG_FILE = path.join(process.cwd(), 'logger-config.json');

/**
 * Log level hierarchy for comparison
 * Lower number = more verbose
 */
const LEVEL_HIERARCHY: Record<LogLevel, number> = {
  debug: 0,
  info: 1,
  warn: 2,
  error: 3,
  silent: 4,
};

/**
 * Default log configuration - all channels set to 'info'
 */
const DEFAULT_CONFIG: LogConfig = {
  SYSTEM: 'info',
  HTTP: 'info',
  DB: 'info',
  SOCKET: 'info',
  AUTH: 'info',
  CRON: 'info',
};

/**
 * LoggerService - Singleton for dynamic log management
 * Uses pino as the underlying logger with channel-based level control
 */
class LoggerService {
  private static instance: LoggerService;
  private config: LogConfig;
  private pinoLogger: pino.Logger;
  private channelLoggers: Map<LogChannel, ChannelLoggerImpl>;

  private constructor() {
    this.config = { ...DEFAULT_CONFIG };

    // Load config from file if it exists
    if (fs.existsSync(CONFIG_FILE)) {
      try {
        const fileContent = fs.readFileSync(CONFIG_FILE, 'utf-8');
        const savedConfig = JSON.parse(fileContent) as Partial<LogConfig>;
        // Merge saved config with defaults
        this.config = { ...DEFAULT_CONFIG, ...savedConfig };
      } catch (error) {
        // If file is corrupted, use defaults
        console.warn('Failed to load logger config from file, using defaults');
      }
    }

    // Initialize pino logger with pino-pretty in dev mode
    const isDev = process.env.NODE_ENV !== 'production';
    this.pinoLogger = pino(
      isDev
        ? pinoPretty({
            colorize: true,
            translateTime: 'SYS:standard',
            ignore: 'pid,hostname',
          })
        : {}
    );

    // Create channel loggers cache
    this.channelLoggers = new Map();
  }

  /**
   * Get the singleton instance
   */
  public static getInstance(): LoggerService {
    if (!LoggerService.instance) {
      LoggerService.instance = new LoggerService();
    }
    return LoggerService.instance;
  }

  /**
   * Get a channel logger with level checking
   * @param channel - The log channel to get
   * @returns Channel logger with info, warn, error, debug methods
   */
  public get(channel: LogChannel): ChannelLogger {
    // Check if we already have a cached logger for this channel
    if (this.channelLoggers.has(channel)) {
      return this.channelLoggers.get(channel)!;
    }

    // Create a new channel logger
    const channelLogger = new ChannelLoggerImpl(
      channel,
      this.pinoLogger,
      () => this.config[channel]
    );

    this.channelLoggers.set(channel, channelLogger);
    return channelLogger;
  }

  /**
   * Update the log level for a specific channel
   * @param channel - The channel to update
   * @param level - The new log level
   */
  public updateConfig(channel: LogChannel, level: LogLevel): void {
    this.config[channel] = level;
    // Persist config to file immediately
    this.persistConfig();
  }

  /**
   * Persist current configuration to file
   */
  private persistConfig(): void {
    try {
      fs.writeFileSync(CONFIG_FILE, JSON.stringify(this.config, null, 2), 'utf-8');
    } catch (error) {
      console.error('Failed to persist logger config to file:', error);
    }
  }

  /**
   * Get the current log configuration
   * @returns Current log configuration
   */
  public getConfig(): LogConfig {
    return { ...this.config };
  }

  /**
   * Reset all channels to default configuration
   */
  public resetConfig(): void {
    this.config = { ...DEFAULT_CONFIG };
    // Persist reset config to file
    this.persistConfig();
  }
}

/**
 * Channel logger with level checking
 * Each log method checks if the current channel level allows it
 */
class ChannelLoggerImpl {
  private channel: LogChannel;
  private pinoLogger: pino.Logger;
  private getLevel: () => LogLevel;
  private bindings: Record<string, any>;

  constructor(
    channel: LogChannel,
    pinoLogger: pino.Logger,
    getLevel: () => LogLevel,
    bindings: Record<string, any> = {}
  ) {
    this.channel = channel;
    this.pinoLogger = pinoLogger;
    this.getLevel = getLevel;
    this.bindings = bindings;
  }

  /**
   * Create a child logger with additional bindings
   * @param bindings - Additional key-value pairs to include in log entries
   * @returns A new ChannelLogger with the bindings merged
   */
  public child(bindings: Record<string, any>): ChannelLogger {
    return new ChannelLoggerImpl(
      this.channel,
      this.pinoLogger,
      this.getLevel,
      { ...this.bindings, ...bindings }
    );
  }

  /**
   * Check if a log level should be logged based on current channel level
   * @param messageLevel - The level of the message being logged
   * @returns true if the message should be logged
   */
  private shouldLog(messageLevel: LogLevel): boolean {
    const currentLevel = this.getLevel();
    const currentLevelValue = LEVEL_HIERARCHY[currentLevel];
    const messageLevelValue = LEVEL_HIERARCHY[messageLevel];

    // Log if message level is >= current level (silent is special case)
    if (currentLevel === 'silent') {
      return false;
    }

    return messageLevelValue >= currentLevelValue;
  }

  /**
   * Log debug message
   */
  public debug(message: string, ...args: any[]): void {
    if (this.shouldLog('debug')) {
      this.pinoLogger.debug({ channel: this.channel, ...this.bindings }, message, ...args);
    }
  }

  /**
   * Log info message
   */
  public info(message: string, ...args: any[]): void {
    if (this.shouldLog('info')) {
      this.pinoLogger.info({ channel: this.channel, ...this.bindings }, message, ...args);
    }
  }

  /**
   * Log warning message
   */
  public warn(message: string, ...args: any[]): void {
    if (this.shouldLog('warn')) {
      this.pinoLogger.warn({ channel: this.channel, ...this.bindings }, message, ...args);
    }
  }

  /**
   * Log error message
   */
  public error(message: string, ...args: any[]): void {
    if (this.shouldLog('error')) {
      this.pinoLogger.error({ channel: this.channel, ...this.bindings }, message, ...args);
    }
  }
}

/**
 * Type definition for channel logger
 */
export type ChannelLogger = {
  debug: (message: string, ...args: any[]) => void;
  info: (message: string, ...args: any[]) => void;
  warn: (message: string, ...args: any[]) => void;
  error: (message: string, ...args: any[]) => void;
  child: (bindings: Record<string, any>) => ChannelLogger;
};

/**
 * Export singleton instance
 */
export const loggerService = LoggerService.getInstance();

/**
 * Legacy export for backward compatibility
 * @deprecated Use loggerService.get(channel) instead
 */
export const logger = {
  info: (message: string, ...args: any[]) => {
    loggerService.get('SYSTEM').info(message, ...args);
  },
  error: (message: string, ...args: any[]) => {
    loggerService.get('SYSTEM').error(message, ...args);
  },
  warn: (message: string, ...args: any[]) => {
    loggerService.get('SYSTEM').warn(message, ...args);
  },
};
