/**
 * Enhanced Structured Logging System for Brainy
 * Provides production-ready logging with structured output, context preservation,
 * performance tracking, and multiple transport support
 */

import { performance } from 'perf_hooks'
import { randomUUID } from '../universal/crypto.js'

export enum LogLevel {
  SILENT = -1,
  FATAL = 0,
  ERROR = 1,
  WARN = 2,
  INFO = 3,
  DEBUG = 4,
  TRACE = 5
}

export interface LogContext {
  requestId?: string
  userId?: string
  operation?: string
  entityId?: string
  entityType?: string
  [key: string]: any
}

export interface LogEntry {
  timestamp: string
  level: string
  levelNumeric: number
  module: string
  message: string
  context?: LogContext
  data?: any
  error?: {
    name: string
    message: string
    stack?: string
    code?: string
  }
  performance?: {
    duration?: number
    memory?: {
      used: number
      total: number
    }
  }
  host?: string
  pid: number
  version?: string
}

export interface LogTransport {
  name: string
  log(entry: LogEntry): void | Promise<void>
  flush?(): Promise<void>
}

export interface StructuredLoggerConfig {
  level: LogLevel
  modules?: Record<string, LogLevel>
  format: 'json' | 'pretty' | 'simple'
  transports: LogTransport[]
  context?: LogContext
  includeHost?: boolean
  includeMemory?: boolean
  bufferSize?: number
  flushInterval?: number
  version?: string
}

class ConsoleTransport implements LogTransport {
  name = 'console'
  private format: 'json' | 'pretty' | 'simple'

  constructor(format: 'json' | 'pretty' | 'simple' = 'json') {
    this.format = format
  }

  log(entry: LogEntry): void {
    const method = this.getConsoleMethod(entry.levelNumeric)
    
    if (this.format === 'json') {
      method(JSON.stringify(entry))
    } else if (this.format === 'pretty') {
      const color = this.getColor(entry.levelNumeric)
      const prefix = `${entry.timestamp} ${color}[${entry.level}]\\x1b[0m [${entry.module}]`
      const message = entry.message
      
      if (entry.error) {
        method(`${prefix} ${message}`, entry.error)
      } else if (entry.data) {
        method(`${prefix} ${message}`, entry.data)
      } else {
        method(`${prefix} ${message}`)
      }
    } else {
      // Simple format
      method(`[${entry.level}] ${entry.message}`)
    }
  }

  private getConsoleMethod(level: number): (...args: any[]) => void {
    switch (level) {
      case LogLevel.FATAL:
      case LogLevel.ERROR:
        return console.error
      case LogLevel.WARN:
        return console.warn
      case LogLevel.INFO:
        return console.info
      default:
        return console.log
    }
  }

  private getColor(level: number): string {
    switch (level) {
      case LogLevel.FATAL:
        return '\\x1b[35m' // Magenta
      case LogLevel.ERROR:
        return '\\x1b[31m' // Red
      case LogLevel.WARN:
        return '\\x1b[33m' // Yellow
      case LogLevel.INFO:
        return '\\x1b[36m' // Cyan
      case LogLevel.DEBUG:
        return '\\x1b[32m' // Green
      case LogLevel.TRACE:
        return '\\x1b[90m' // Gray
      default:
        return '\\x1b[0m' // Reset
    }
  }
}

class BufferedTransport implements LogTransport {
  name = 'buffered'
  private buffer: LogEntry[] = []
  private innerTransport: LogTransport
  private bufferSize: number
  private flushTimer?: NodeJS.Timeout

  constructor(innerTransport: LogTransport, bufferSize: number = 100, flushInterval: number = 5000) {
    this.innerTransport = innerTransport
    this.bufferSize = bufferSize
    
    if (flushInterval > 0) {
      this.flushTimer = setInterval(() => this.flush(), flushInterval)
    }
  }

  log(entry: LogEntry): void {
    this.buffer.push(entry)
    
    if (this.buffer.length >= this.bufferSize) {
      this.flush()
    }
  }

  async flush(): Promise<void> {
    const entries = this.buffer.splice(0)
    for (const entry of entries) {
      await this.innerTransport.log(entry)
    }
    
    if (this.innerTransport.flush) {
      await this.innerTransport.flush()
    }
  }

  destroy(): void {
    if (this.flushTimer) {
      clearInterval(this.flushTimer)
    }
    this.flush()
  }
}

export class StructuredLogger {
  private static instance: StructuredLogger
  private config: StructuredLoggerConfig
  private defaultContext: LogContext = {}
  private performanceMarks = new Map<string, number>()

  private constructor() {
    const isDevelopment = process.env.NODE_ENV !== 'production'
    const format = isDevelopment ? 'pretty' : 'json'
    
    this.config = {
      level: isDevelopment ? LogLevel.DEBUG : LogLevel.INFO,
      format,
      transports: [new ConsoleTransport(format)],
      includeHost: !isDevelopment,
      includeMemory: false,
      bufferSize: 100,
      flushInterval: 5000,
      version: process.env.npm_package_version
    }

    // Load from environment
    this.loadEnvironmentConfig()
  }

  private loadEnvironmentConfig(): void {
    const envLevel = process.env.BRAINY_LOG_LEVEL
    if (envLevel) {
      const level = LogLevel[envLevel.toUpperCase() as keyof typeof LogLevel]
      if (level !== undefined) {
        this.config.level = level
      }
    }

    const envFormat = process.env.BRAINY_LOG_FORMAT
    if (envFormat && ['json', 'pretty', 'simple'].includes(envFormat)) {
      this.config.format = envFormat as 'json' | 'pretty' | 'simple'
    }

    const moduleConfig = process.env.BRAINY_MODULE_LOG_LEVELS
    if (moduleConfig) {
      try {
        this.config.modules = JSON.parse(moduleConfig)
      } catch {
        // Ignore parse errors
      }
    }
  }

  static getInstance(): StructuredLogger {
    if (!StructuredLogger.instance) {
      StructuredLogger.instance = new StructuredLogger()
    }
    return StructuredLogger.instance
  }

  configure(config: Partial<StructuredLoggerConfig>): void {
    this.config = { ...this.config, ...config }
  }

  setContext(context: LogContext): void {
    this.defaultContext = { ...this.defaultContext, ...context }
  }

  clearContext(): void {
    this.defaultContext = {}
  }

  withContext(context: LogContext): StructuredLogger {
    const contextualLogger = Object.create(this)
    contextualLogger.defaultContext = { ...this.defaultContext, ...context }
    return contextualLogger
  }

  startTimer(label: string): void {
    this.performanceMarks.set(label, performance.now())
  }

  endTimer(label: string): number | undefined {
    const start = this.performanceMarks.get(label)
    if (start === undefined) return undefined
    
    const duration = performance.now() - start
    this.performanceMarks.delete(label)
    return duration
  }

  private shouldLog(level: LogLevel, module: string): boolean {
    if (this.config.modules?.[module] !== undefined) {
      return level <= this.config.modules[module]
    }
    return level <= this.config.level
  }

  private createLogEntry(
    level: LogLevel,
    module: string,
    message: string,
    context?: LogContext,
    data?: any,
    error?: Error
  ): LogEntry {
    const entry: LogEntry = {
      timestamp: new Date().toISOString(),
      level: LogLevel[level],
      levelNumeric: level,
      module,
      message,
      pid: process.pid,
      version: this.config.version
    }

    // Merge contexts
    const mergedContext = { ...this.defaultContext, ...context }
    if (Object.keys(mergedContext).length > 0) {
      entry.context = mergedContext
    }

    if (data !== undefined) {
      entry.data = data
    }

    if (error) {
      entry.error = {
        name: error.name,
        message: error.message,
        stack: error.stack,
        code: (error as any).code
      }
    }

    if (this.config.includeHost) {
      // Use dynamic import for hostname (Node.js only)
      if (typeof window === 'undefined') {
        try {
          const os = require('node:os')
          entry.host = os.hostname()
        } catch {
          entry.host = 'unknown'
        }
      } else {
        entry.host = window.location?.hostname || 'browser'
      }
    }

    if (this.config.includeMemory && typeof process !== 'undefined' && process.memoryUsage) {
      const mem = process.memoryUsage()
      entry.performance = {
        memory: {
          used: Math.round(mem.heapUsed / 1024 / 1024),
          total: Math.round(mem.heapTotal / 1024 / 1024)
        }
      }
    }

    return entry
  }

  private log(
    level: LogLevel,
    module: string,
    message: string,
    contextOrData?: LogContext | any,
    data?: any
  ): void {
    if (!this.shouldLog(level, module)) {
      return
    }

    // Handle overloaded parameters
    let context: LogContext | undefined
    let logData: any
    
    if (contextOrData && typeof contextOrData === 'object' && !Array.isArray(contextOrData)) {
      // Check if it looks like a context object
      const hasContextKeys = ['requestId', 'userId', 'operation', 'entityId', 'entityType']
        .some(key => key in contextOrData)
      
      if (hasContextKeys) {
        context = contextOrData
        logData = data
      } else {
        logData = contextOrData
      }
    } else {
      logData = contextOrData
    }

    // Extract error if present
    let error: Error | undefined
    if (logData instanceof Error) {
      error = logData
      logData = undefined
    } else if (logData?.error instanceof Error) {
      error = logData.error
      delete logData.error
    }

    const entry = this.createLogEntry(level, module, message, context, logData, error)

    // Send to all transports
    for (const transport of this.config.transports) {
      try {
        transport.log(entry)
      } catch (err) {
        // Fallback to console.error if transport fails
        console.error('Logger transport error:', err)
      }
    }
  }

  fatal(module: string, message: string, contextOrData?: LogContext | any, data?: any): void {
    this.log(LogLevel.FATAL, module, message, contextOrData, data)
  }

  error(module: string, message: string, contextOrData?: LogContext | any, data?: any): void {
    this.log(LogLevel.ERROR, module, message, contextOrData, data)
  }

  warn(module: string, message: string, contextOrData?: LogContext | any, data?: any): void {
    this.log(LogLevel.WARN, module, message, contextOrData, data)
  }

  info(module: string, message: string, contextOrData?: LogContext | any, data?: any): void {
    this.log(LogLevel.INFO, module, message, contextOrData, data)
  }

  debug(module: string, message: string, contextOrData?: LogContext | any, data?: any): void {
    this.log(LogLevel.DEBUG, module, message, contextOrData, data)
  }

  trace(module: string, message: string, contextOrData?: LogContext | any, data?: any): void {
    this.log(LogLevel.TRACE, module, message, contextOrData, data)
  }

  createModuleLogger(module: string) {
    const self = this
    return {
      fatal: (message: string, contextOrData?: LogContext | any, data?: any) => 
        self.fatal(module, message, contextOrData, data),
      error: (message: string, contextOrData?: LogContext | any, data?: any) => 
        self.error(module, message, contextOrData, data),
      warn: (message: string, contextOrData?: LogContext | any, data?: any) => 
        self.warn(module, message, contextOrData, data),
      info: (message: string, contextOrData?: LogContext | any, data?: any) => 
        self.info(module, message, contextOrData, data),
      debug: (message: string, contextOrData?: LogContext | any, data?: any) => 
        self.debug(module, message, contextOrData, data),
      trace: (message: string, contextOrData?: LogContext | any, data?: any) => 
        self.trace(module, message, contextOrData, data),
      withContext: (context: LogContext) => {
        const contextual = self.withContext(context)
        return contextual.createModuleLogger(module)
      },
      startTimer: (label: string) => self.startTimer(`${module}:${label}`),
      endTimer: (label: string) => self.endTimer(`${module}:${label}`)
    }
  }

  async flush(): Promise<void> {
    const flushPromises = this.config.transports
      .filter(t => t.flush)
      .map(t => t.flush!())
    
    await Promise.all(flushPromises)
  }

  addTransport(transport: LogTransport): void {
    this.config.transports.push(transport)
  }

  removeTransport(name: string): void {
    this.config.transports = this.config.transports.filter(t => t.name !== name)
  }

  child(context: LogContext): StructuredLogger {
    return this.withContext(context)
  }
}

// Singleton instance
export const structuredLogger = StructuredLogger.getInstance()

// Convenience functions
export function createModuleLogger(module: string) {
  return structuredLogger.createModuleLogger(module)
}

export function setLogContext(context: LogContext) {
  structuredLogger.setContext(context)
}

export function withLogContext(context: LogContext) {
  return structuredLogger.withContext(context)
}

// Correlation ID middleware helper
export function createCorrelationId(): string {
  return randomUUID()
}

// Performance logging helper
export function logPerformance(
  logger: ReturnType<typeof createModuleLogger>,
  operation: string,
  fn: () => any
): any {
  logger.startTimer(operation)
  try {
    const result = fn()
    if (result && typeof result.then === 'function') {
      return result.finally(() => {
        const duration = logger.endTimer(operation)
        logger.debug(`${operation} completed`, { duration })
      })
    }
    const duration = logger.endTimer(operation)
    logger.debug(`${operation} completed`, { duration })
    return result
  } catch (error) {
    const duration = logger.endTimer(operation)
    logger.error(`${operation} failed`, { duration, error })
    throw error
  }
}

// Backward compatibility wrapper for existing logger
export class LoggerCompatibilityWrapper {
  private moduleLogger: ReturnType<typeof createModuleLogger>

  constructor(module: string = 'legacy') {
    this.moduleLogger = createModuleLogger(module)
  }

  error(module: string, message: string, ...args: any[]): void {
    this.moduleLogger.error(message, { module, data: args })
  }

  warn(module: string, message: string, ...args: any[]): void {
    this.moduleLogger.warn(message, { module, data: args })
  }

  info(module: string, message: string, ...args: any[]): void {
    this.moduleLogger.info(message, { module, data: args })
  }

  debug(module: string, message: string, ...args: any[]): void {
    this.moduleLogger.debug(message, { module, data: args })
  }

  trace(module: string, message: string, ...args: any[]): void {
    this.moduleLogger.trace(message, { module, data: args })
  }

  createModuleLogger(module: string) {
    const logger = createModuleLogger(module)
    return {
      error: (message: string, ...args: any[]) => logger.error(message, { data: args }),
      warn: (message: string, ...args: any[]) => logger.warn(message, { data: args }),
      info: (message: string, ...args: any[]) => logger.info(message, { data: args }),
      debug: (message: string, ...args: any[]) => logger.debug(message, { data: args }),
      trace: (message: string, ...args: any[]) => logger.trace(message, { data: args })
    }
  }
}

// Export types for external use
export type ModuleLogger = ReturnType<typeof createModuleLogger>
// Types are already exported above, no need to re-export