import winston from "winston";
import fs from 'fs';
import path from 'path';
import { nodejsRoot } from "../projectrootpath";

/**
 * Log levels for different types of messages
 */
export enum LogLevel {
  INFO = 'INFO',
  WARN = 'WARN',
  ERROR = 'ERROR',
  DEBUG = 'DEBUG'
}

/**
 * Interface for logger configuration
 */
export interface LoggerConfig {
  /** Path to the log file */
  filePath: string;
  /** Whether to also log to console */
  logToConsole?: boolean;
  /** Minimum log level to record */
  minLevel?: LogLevel;
}

/**
 * Logger class for writing logs to a file
 */
export class Logger {
  private filePath: string;
  private logToConsole: boolean;
  private minLevel: LogLevel;
  
  /**
   * Creates a new Logger instance
   * @param config Logger configuration
   */
  constructor(config: LoggerConfig) {
    this.filePath = config.filePath;
    this.logToConsole = config.logToConsole ?? true;
    this.minLevel = config.minLevel ?? LogLevel.INFO;
    
    // Ensure the directory exists
    const dir = path.dirname(this.filePath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
  }
  
  /**
   * Writes a log entry to the file
   * @param level Log level
   * @param message Log message
   * @param data Additional data to log (optional)
   */
  private log(level: LogLevel, message: string, data?: any): void {
    // Skip if below minimum log level
    if (this.shouldSkip(level)) {
      return;
    }
    
    const timestamp = new Date().toISOString();
    let logEntry = `[${timestamp}] [${level}] ${message}`;
    
    if (data) {
      if (typeof data === 'object') {
        try {
          logEntry += `\n${JSON.stringify(data, null, 2)}`;
        } catch (err) {
          logEntry += `\n[Object serialization failed]`;
        }
      } else {
        logEntry += `\n${data}`;
      }
    }
    
    logEntry += '\n';
    
    // Append to file
    fs.appendFileSync(this.filePath, logEntry);
    
    // Log to console if enabled
    if (this.logToConsole) {
      const consoleMethod = this.getConsoleMethod(level);
      consoleMethod(logEntry);
    }
  }
  
  /**
   * Determines if a log entry should be skipped based on minimum log level
   */
  private shouldSkip(level: LogLevel): boolean {
    const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];
    const minLevelIndex = levels.indexOf(this.minLevel);
    const currentLevelIndex = levels.indexOf(level);
    
    return currentLevelIndex < minLevelIndex;
  }
  
  /**
   * Gets the appropriate console method for the log level
   */
  private getConsoleMethod(level: LogLevel): (message: string) => void {
    switch (level) {
      case LogLevel.ERROR:
        return console.error;
      case LogLevel.WARN:
        return console.warn;
      case LogLevel.DEBUG:
        return console.debug;
      case LogLevel.INFO:
      default:
        return console.log;
    }
  }
  
  /**
   * Logs an info message
   * @param message Log message
   * @param data Additional data (optional)
   */
  public info(message: string, data?: any): void {
    this.log(LogLevel.INFO, message, data);
  }
  
  /**
   * Logs a warning message
   * @param message Log message
   * @param data Additional data (optional)
   */
  public warn(message: string, data?: any): void {
    this.log(LogLevel.WARN, message, data);
  }
  
  /**
   * Logs an error message
   * @param message Log message
   * @param error Error object or additional data (optional)
   */
  public error(message: string, error?: any): void {
    this.log(LogLevel.ERROR, message, error);
  }
  
  /**
   * Logs a debug message
   * @param message Log message
   * @param data Additional data (optional)
   */
  public debug(message: string, data?: any): void {
    this.log(LogLevel.DEBUG, message, data);
  }
  
  /**
   * Creates a new logger instance with a default configuration
   * @param filePath Path to the log file
   * @returns Logger instance
   */
  public static createLogger(filePath: string): Logger {
    return new Logger({ filePath });
  }
}

/**
 * Creates a default logger that writes to a file in the same directory
 * @param filename Name of the log file
 * @returns Logger instance
 */
export function createDefaultLogger(filename: string): Logger {
  const filePath = path.join(__dirname, filename);
  return Logger.createLogger(filePath);
}

export function createWinstonLogger(toConsole = true, rootName = ".") {
  const combinedLogsFile = path.resolve(nodejsRoot, "logs", rootName, "combined.log");
  const errorLogsFile = path.resolve(nodejsRoot, "logs", rootName, "error.log");
  console.log("Logs will be written to: ", combinedLogsFile, errorLogsFile);
  const transports = [
    new winston.transports.Console(),
    new winston.transports.File({ filename: errorLogsFile, level: "error" }),
    new winston.transports.File({ filename: combinedLogsFile }),
  ];

  if (!toConsole) {
    transports.shift();
  }

  const logger = winston.createLogger({
    // level: "info",
    format: winston.format.combine(
      // winston.format.timestamp(),
      winston.format.printf((info) => {
        return `${info.level}: ${info.message}` ;
      })
    ),
    transports
  });
  return logger;
}

export const logger = createWinstonLogger(true);