const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');

// Define log levels and colors
const logLevels = {
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  debug: 4,
};

winston.addColors({
  error: 'red',
  warn: 'yellow',
  info: 'green',
  http: 'magenta',
  debug: 'white',
});

// Determine log level based on environment
const level = () => {
  const env = process.env.NODE_ENV || 'dev';
  const isDevelopment = env === 'dev' || env === 'development';
  return isDevelopment ? 'debug' : process.env.LOG_LEVEL || 'info';
};

// Custom format to extract stack traces from Error objects in metadata
const extractErrorStack = winston.format((info) => {
  // Check if any metadata value is an Error object and extract its stack
  Object.keys(info).forEach(key => {
    if (info[key] instanceof Error) {
      info[key] = {
        message: info[key].message,
        stack: info[key].stack,
        name: info[key].name
      };
    }
  });
  return info;
});

// Define log format
const logFormat = winston.format.combine(
  winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
  winston.format.colorize({ all: true }),
  extractErrorStack(),
  winston.format.printf((info) => {
    const { timestamp, level, message, stack, ...metadata } = info;
    let output = `${timestamp} ${level}: ${message}`;

    // Include relevant metadata in console output
    const metaKeys = Object.keys(metadata).filter(key =>
      !['splat', 'Symbol(level)', 'Symbol(message)'].includes(key) &&
      !key.startsWith('Symbol')
    );

    if (metaKeys.length > 0) {
      const metaOutput = {};
      metaKeys.forEach(key => {
        // For Error objects, extract message and stack
        if (metadata[key] && metadata[key].stack) {
          metaOutput[key] = { message: metadata[key].message, stack: metadata[key].stack };
        } else {
          metaOutput[key] = metadata[key];
        }
      });
      output += ` ${JSON.stringify(metaOutput, null, 2)}`;
    }

    // Check for stack trace in the info object itself
    if (stack) {
      output += `\nStack: ${stack}`;
    }

    return output;
  }),
);

// Define JSON format for file logging
const jsonFormat = winston.format.combine(
  winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
  winston.format.errors({ stack: true }),
  extractErrorStack(),
  winston.format.json()
);

// Create transports array
const transports = [
  // Console transport for development
  new winston.transports.Console({
    level: level(),
    format: logFormat,
  }),

  // Daily rotate file for all logs
  new DailyRotateFile({
    filename: 'logs/application-%DATE%.log',
    datePattern: 'YYYY-MM-DD',
    maxSize: '20m',
    maxFiles: '14d',
    level: 'info',
    format: jsonFormat,
  }),

  // Daily rotate file for error logs only
  new DailyRotateFile({
    filename: 'logs/error-%DATE%.log',
    datePattern: 'YYYY-MM-DD',
    maxSize: '20m',
    maxFiles: '14d',
    level: 'error',
    format: jsonFormat,
  }),
];

// Create the logger
const logger = winston.createLogger({
  level: level(),
  levels: logLevels,
  format: jsonFormat,
  transports,
  exceptionHandlers: [
    new winston.transports.File({ filename: 'logs/exceptions.log' })
  ],
  rejectionHandlers: [
    new winston.transports.File({ filename: 'logs/rejections.log' })
  ],
});

// Create a stream object for Morgan
logger.stream = {
  write: (message) => {
    // Remove trailing newline from Morgan and log as http level
    logger.http(message.trim());
  },
};

// Add request ID correlation function
logger.withRequestId = (requestId) => {
  return {
    error: (message, meta = {}) => logger.error(message, { ...meta, requestId }),
    warn: (message, meta = {}) => logger.warn(message, { ...meta, requestId }),
    info: (message, meta = {}) => logger.info(message, { ...meta, requestId }),
    http: (message, meta = {}) => logger.http(message, { ...meta, requestId }),
    debug: (message, meta = {}) => logger.debug(message, { ...meta, requestId }),
  };
};

// Add sanitization helper for sensitive data
logger.sanitize = (data) => {
  if (typeof data !== 'object' || data === null) {
    return data;
  }

  const sensitiveFields = [
    'password', 'token', 'secret', 'key', 'authorization', 'cookie',
    'ssn', 'credit_card', 'cvv', 'pin', 'account_number'
  ];

  const sanitized = JSON.parse(JSON.stringify(data));

  const sanitizeObject = (obj) => {
    for (const [key, value] of Object.entries(obj)) {
      const lowerKey = key.toLowerCase();
      if (sensitiveFields.some(field => lowerKey.includes(field))) {
        obj[key] = '[REDACTED]';
      } else if (typeof value === 'object' && value !== null) {
        sanitizeObject(value);
      }
    }
  };

  sanitizeObject(sanitized);
  return sanitized;
};

module.exports = logger;