// @lifecycle canonical - Primary logger implementation and console adapters.
/**
 * Logging Module
 * Handles file logging and transport-aware console logging
 */

import { appendFile, writeFile } from 'node:fs/promises';

import { LogLevel, TransportType } from '../../shared/types/index.js';

// Logger interface is defined in shared/types/ (Layer 0) for cross-layer access.
// Re-exported here for backward compatibility.
export type { Logger } from '../../shared/types/index.js';
import type { Logger } from '../../shared/types/index.js';

/**
 * Log entry for the in-memory ring buffer (exposed via MCP resources)
 */
export interface LogEntry {
  id: string;
  timestamp: number;
  level: 'error' | 'warn' | 'info' | 'debug';
  message: string;
  context?: Record<string, unknown>;
}

/**
 * Options for retrieving recent logs
 */
export interface GetRecentLogsOptions {
  level?: 'error' | 'warn' | 'info' | 'debug';
  limit?: number;
}

/**
 * Logging configuration options for EnhancedLogger
 */
export interface EnhancedLoggingConfig {
  logFile: string;
  transport: string;
  enableDebug?: boolean;
  configuredLevel?: string;
  /** Maximum entries to retain in ring buffer (default: 500) */
  maxBufferEntries?: number;
  /** Minimum level to buffer for resource access (default: 'info') */
  bufferLevel?: string;
}

/**
 * Enhanced logger implementation with file and console logging
 */
export class EnhancedLogger implements Logger {
  private logFile: string;
  private transport: string;
  private enableDebug: boolean;
  private isCI: boolean;
  private configuredLevel: LogLevel;
  private static readonly LOG_LEVEL_PRIORITY = {
    [LogLevel.ERROR]: 0,
    [LogLevel.WARN]: 1,
    [LogLevel.INFO]: 2,
    [LogLevel.DEBUG]: 3,
  };

  // Ring buffer for MCP resources access
  private logBuffer: LogEntry[] = [];
  private readonly maxBufferSize: number;
  private nextEntryId: number = 1;
  private bufferLevel: LogLevel;

  constructor(config: EnhancedLoggingConfig) {
    this.logFile = config.logFile;
    this.transport = config.transport;
    this.enableDebug = config.enableDebug || false;
    this.isCI = process.env['CI'] === 'true' || process.env['NODE_ENV'] === 'test';

    // Map config level to LogLevel enum with fallback to INFO
    this.configuredLevel = this.parseLogLevel(config.configuredLevel || 'info');

    // Ring buffer configuration
    this.maxBufferSize = config.maxBufferEntries ?? 500;
    this.bufferLevel = this.parseLogLevel(config.bufferLevel ?? 'info');
  }

  /**
   * Parse string log level to LogLevel enum
   */
  private parseLogLevel(level: string): LogLevel {
    const normalizedLevel = level.toUpperCase();
    switch (normalizedLevel) {
      case 'DEBUG':
        return LogLevel.DEBUG;
      case 'INFO':
        return LogLevel.INFO;
      case 'WARN':
        return LogLevel.WARN;
      case 'ERROR':
        return LogLevel.ERROR;
      default:
        console.warn(`Unknown log level "${level}", defaulting to INFO`);
        return LogLevel.INFO;
    }
  }

  /**
   * Check if a log level should be output based on configuration
   */
  private shouldLog(level: LogLevel): boolean {
    // Command-line flags override config
    if (this.enableDebug) {
      return true; // Show everything in debug mode
    }

    const levelPriority = EnhancedLogger.LOG_LEVEL_PRIORITY[level];
    const configPriority = EnhancedLogger.LOG_LEVEL_PRIORITY[this.configuredLevel];

    return levelPriority <= configPriority;
  }

  /**
   * Initialize the log file with a clean start
   */
  async initLogFile(): Promise<void> {
    try {
      const timestamp = new Date().toISOString();
      await writeFile(this.logFile, `--- MCP Server Log Started at ${timestamp} ---\n`, 'utf8');
    } catch (error) {
      console.error(`Error initializing log file:`, error);
    }
  }

  /**
   * Write a message to the log file
   */
  private async logToFile(level: LogLevel, message: string, ...args: any[]): Promise<void> {
    // Check if this log level should be output based on configuration
    if (!this.shouldLog(level)) {
      return;
    }

    try {
      let logMessage = `[${new Date().toISOString()}] [${level}] ${message}`;
      if (args.length > 0) {
        logMessage += ` ${args
          .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : arg))
          .join(' ')}`;
      }
      await appendFile(this.logFile, logMessage + '\n', 'utf8');
    } catch (error) {
      console.error('Error writing to log file:', error);
    }
  }

  /**
   * Log to console based on transport and environment
   */
  private logToConsole(level: LogLevel, message: string, ...args: any[]): void {
    // Check if this log level should be output based on configuration
    if (!this.shouldLog(level)) {
      return;
    }

    // In CI environment, always log errors and warnings regardless of transport
    // This ensures critical issues are visible in CI output
    if (this.isCI) {
      if (level === LogLevel.ERROR || level === LogLevel.WARN) {
        switch (level) {
          case LogLevel.ERROR:
            console.error(`[ERROR] ${message}`, ...args);
            break;
          case LogLevel.WARN:
            console.warn(`[WARN] ${message}`, ...args);
            break;
        }
        return;
      }
      // In CI, suppress DEBUG messages unless explicitly enabled
      if (level === LogLevel.DEBUG && !this.enableDebug) {
        return;
      }
    }

    // Standard logging for non-CI environments
    // Always use stderr to avoid corrupting STDIO protocol
    if (this.transport !== TransportType.STDIO) {
      switch (level) {
        case LogLevel.INFO:
          console.error(`[INFO] ${message}`, ...args);
          break;
        case LogLevel.ERROR:
          console.error(`[ERROR] ${message}`, ...args);
          break;
        case LogLevel.WARN:
          console.warn(`[WARN] ${message}`, ...args);
          break;
        case LogLevel.DEBUG:
          console.error(`[DEBUG] ${message}`, ...args);
          break;
      }
    }
  }

  /**
   * Info level logging
   */
  info(message: string, ...args: any[]): void {
    this.logToConsole(LogLevel.INFO, message, ...args);
    this.logToFile(LogLevel.INFO, message, ...args);
    this.addToBuffer(LogLevel.INFO, message, args);
  }

  /**
   * Error level logging
   */
  error(message: string, ...args: any[]): void {
    this.logToConsole(LogLevel.ERROR, message, ...args);
    this.logToFile(LogLevel.ERROR, message, ...args);
    this.addToBuffer(LogLevel.ERROR, message, args);
  }

  /**
   * Warning level logging
   */
  warn(message: string, ...args: any[]): void {
    this.logToConsole(LogLevel.WARN, message, ...args);
    this.logToFile(LogLevel.WARN, message, ...args);
    this.addToBuffer(LogLevel.WARN, message, args);
  }

  /**
   * Debug level logging
   */
  debug(message: string, ...args: any[]): void {
    this.logToConsole(LogLevel.DEBUG, message, ...args);
    this.logToFile(LogLevel.DEBUG, message, ...args);
    this.addToBuffer(LogLevel.DEBUG, message, args);
  }

  /**
   * Update transport type (useful when transport is determined after logger creation)
   */
  setTransport(transport: string): void {
    this.transport = transport;
  }

  /**
   * Enable or disable debug logging
   */
  setDebugEnabled(enabled: boolean): void {
    this.enableDebug = enabled;
  }

  /**
   * Log startup information
   */
  logStartupInfo(transport: string, config: any): void {
    this.info(`Server starting up - Process ID: ${process.pid}`);
    this.info(`Node version: ${process.version}`);
    this.info(`Working directory: ${process.cwd()}`);
    this.info(`Using transport: ${transport}`);
    this.info(`Command-line arguments: ${JSON.stringify(process.argv)}`);
    this.debug('Configuration:', JSON.stringify(config, null, 2));
  }

  /**
   * Log memory usage information
   */
  logMemoryUsage(): void {
    this.info(`Server process memory usage: ${JSON.stringify(process.memoryUsage())}`);
  }

  // ─────────────────────────────────────────────────────────────────────────────
  // Ring Buffer Methods (for MCP resources access)
  // ─────────────────────────────────────────────────────────────────────────────

  /**
   * Add a log entry to the ring buffer
   */
  private addToBuffer(level: LogLevel, message: string, args: unknown[]): void {
    // Check if this level should be buffered
    const levelPriority = EnhancedLogger.LOG_LEVEL_PRIORITY[level];
    const bufferPriority = EnhancedLogger.LOG_LEVEL_PRIORITY[this.bufferLevel];
    if (levelPriority > bufferPriority) {
      return; // Skip entries below buffer threshold
    }

    const entry: LogEntry = {
      id: `log-${this.nextEntryId++}`,
      timestamp: Date.now(),
      level: level.toLowerCase() as LogEntry['level'],
      message,
      context: this.extractContext(args),
    };

    this.logBuffer.push(entry);
    if (this.logBuffer.length > this.maxBufferSize) {
      this.logBuffer.shift(); // Remove oldest entry
    }
  }

  /**
   * Extract structured context from log arguments
   */
  private extractContext(args: unknown[]): Record<string, unknown> | undefined {
    if (args.length === 0) {
      return undefined;
    }

    // Extract component tag from message if present (e.g., "[Pipeline]")
    const context: Record<string, unknown> = {};

    for (const arg of args) {
      if (typeof arg === 'object' && arg !== null) {
        Object.assign(context, arg);
      } else if (typeof arg === 'string') {
        // Check for component pattern like "[Component]"
        const componentMatch = arg.match(/^\[([^\]]+)\]/);
        if (componentMatch !== null) {
          context['component'] = componentMatch[1];
        }
      }
    }

    return Object.keys(context).length > 0 ? context : undefined;
  }

  /**
   * Get recent log entries from the ring buffer
   * @param options.level - Filter to this level and above (e.g., 'warn' returns warn + error)
   * @param options.limit - Maximum entries to return (default: 100)
   */
  getRecentLogs(options?: GetRecentLogsOptions): LogEntry[] {
    const { level, limit = 100 } = options ?? {};
    let filtered = this.logBuffer;

    if (level !== undefined) {
      const levelPriority: Record<string, number> = { error: 0, warn: 1, info: 2, debug: 3 };
      const maxPriority = levelPriority[level] ?? 3;
      filtered = filtered.filter((e) => (levelPriority[e.level] ?? 3) <= maxPriority);
    }

    // Return newest first, limited
    return filtered.slice(-limit).reverse();
  }

  /**
   * Get a specific log entry by ID
   */
  getLogEntry(id: string): LogEntry | undefined {
    return this.logBuffer.find((e) => e.id === id);
  }

  /**
   * Get buffer statistics
   */
  getBufferStats(): { count: number; maxSize: number; oldestId: string | null } {
    const oldest = this.logBuffer[0];
    return {
      count: this.logBuffer.length,
      maxSize: this.maxBufferSize,
      oldestId: oldest?.id ?? null,
    };
  }
}

/**
 * Create a logger instance
 */
export function createLogger(config: EnhancedLoggingConfig): EnhancedLogger {
  return new EnhancedLogger(config);
}

/**
 * Helper to build a logger configuration with sensible defaults.
 * Allows subsystems to opt into lightweight logging without duplicating paths.
 */
export function getDefaultLoggerConfig(
  overrides: Partial<EnhancedLoggingConfig> = {}
): EnhancedLoggingConfig {
  // Require explicit logFile - no process.cwd() guessing
  // Callers should pass logFile from PathResolver or ConfigManager
  if (!overrides.logFile) {
    throw new Error(
      'getDefaultLoggerConfig requires logFile in overrides. ' +
        'Use PathResolver.getLogsPath() or ConfigManager to determine log location.'
    );
  }

  return {
    logFile: overrides.logFile,
    transport: overrides.transport ?? TransportType.SSE,
    enableDebug: overrides.enableDebug ?? false,
    configuredLevel: overrides.configuredLevel ?? 'info',
  };
}

/**
 * Create a simple logger for areas that don't need the full enhanced logger
 * Now supports verbosity control via command-line flags
 */
export function createSimpleLogger(transport: string = 'sse'): Logger {
  const enableConsole = transport !== TransportType.STDIO;

  // Check command-line flags for verbosity control
  const args = process.argv.slice(2);
  const isVerbose = args.includes('--verbose') || args.includes('--debug-startup');
  const isQuiet = args.includes('--quiet');

  // Always use stderr to avoid corrupting STDIO protocol
  return {
    info: (message: string, ...args: any[]) => {
      if (enableConsole && !isQuiet) {
        console.error(`[INFO] ${message}`, ...args);
      }
    },
    error: (message: string, ...args: any[]) => {
      if (enableConsole && !isQuiet) {
        console.error(`[ERROR] ${message}`, ...args);
      }
    },
    warn: (message: string, ...args: any[]) => {
      if (enableConsole && !isQuiet) {
        console.warn(`[WARN] ${message}`, ...args);
      }
    },
    debug: (message: string, ...args: any[]) => {
      if (enableConsole && isVerbose) {
        console.error(`[DEBUG] ${message}`, ...args);
      }
    },
  };
}

/**
 * Setup console redirection for STDIO transport
 * This prevents log messages from interfering with JSON MCP messages
 */
export function setupConsoleRedirection(logger: Logger): void {
  const originalConsoleLog = console.log;
  const originalConsoleError = console.error;

  console.log = (...args) => {
    logger.debug('CONSOLE: ' + args.join(' '));
  };

  console.error = (...args) => {
    logger.error('CONSOLE_ERROR: ' + args.join(' '));
  };
}

/**
 * Create a no-op logger for tests and cases where logging isn't needed.
 * All methods are empty functions that discard log messages.
 */
export function createNoopLogger(): Logger {
  return {
    debug: () => {},
    info: () => {},
    warn: () => {},
    error: () => {},
  };
}

/**
 * Pre-instantiated no-op logger singleton for convenience.
 * Use this when you need a logger instance but don't want any output.
 */
export const noopLogger: Logger = createNoopLogger();

/**
 * Setup process event handlers for logging
 */
export function setupProcessEventHandlers(logger: Logger): void {
  // Handle graceful shutdown
  process.on('SIGINT', () => {
    logger.info('Shutting down server...');
    process.exit(0);
  });

  // Handle uncaught exceptions
  process.on('uncaughtException', (error) => {
    logger.error('Uncaught exception:', error);
  });

  // Handle unhandled promise rejections
  process.on('unhandledRejection', (reason, promise) => {
    logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
  });

  // Log when the stdin closes (which happens when the parent process terminates)
  process.stdin.on('end', () => {
    logger.info('STDIN stream ended - parent process may have terminated');
    process.exit(0);
  });
}
