import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Log levels
const LOG_LEVELS = {
  ERROR: 0,
  WARN: 1,
  INFO: 2,
  DEBUG: 3,
};

// ANSI color codes for console output
const COLORS = {
  reset: "\x1b[0m",
  bright: "\x1b[1m",
  dim: "\x1b[2m",
  red: "\x1b[31m",
  green: "\x1b[32m",
  yellow: "\x1b[33m",
  blue: "\x1b[34m",
  magenta: "\x1b[35m",
  cyan: "\x1b[36m",
  white: "\x1b[37m",
};

class Logger {
  constructor() {
    this.logLevel =
      process.env.LOG_LEVEL ||
      (process.env.NODE_ENV === "production" ? "INFO" : "DEBUG");
    this.logToFile = process.env.LOG_TO_FILE === "true";
    this.logDir = path.join(__dirname, "../../logs");

    // Create logs directory if file logging is enabled
    if (this.logToFile && !fs.existsSync(this.logDir)) {
      fs.mkdirSync(this.logDir, { recursive: true });
    }
  }

  /**
   * Format timestamp
   */
  getTimestamp() {
    return new Date().toISOString();
  }

  /**
   * Format log message
   */
  formatMessage(level, message, data = null) {
    const timestamp = this.getTimestamp();

    // Ensure message is always a string
    let messageStr = message;
    if (typeof message === "object" && message !== null) {
      try {
        messageStr = JSON.stringify(message, null, 2);
      } catch (e) {
        messageStr = String(message);
      }
    } else if (message === null || message === undefined) {
      messageStr = String(message);
    }

    const logEntry = {
      timestamp,
      level,
      message: messageStr,
      ...(data && { data }),
    };
    return JSON.stringify(logEntry);
  }

  /**
   * Write to log file
   */
  writeToFile(level, message, data = null) {
    if (!this.logToFile) return;

    try {
      const date = new Date().toISOString().split("T")[0];
      const logFile = path.join(this.logDir, `${date}.log`);
      const logEntry = this.formatMessage(level, message, data);

      fs.appendFileSync(logFile, logEntry + "\n", "utf8");
    } catch (error) {
      // Use console.error with proper error message formatting to avoid [object Object]
      const errorMsg = error instanceof Error ? error.message : String(error);
      const errorStack = error instanceof Error ? error.stack : undefined;
      console.error(`Failed to write to log file: ${errorMsg}`);
      if (errorStack) {
        console.error(`Stack trace: ${errorStack}`);
      }
    }
  }

  /**
   * Get color for log level
   */
  getColor(level) {
    switch (level) {
      case "ERROR":
        return COLORS.red;
      case "WARN":
        return COLORS.yellow;
      case "INFO":
        return COLORS.green;
      case "DEBUG":
        return COLORS.cyan;
      default:
        return COLORS.white;
    }
  }

  /**
   * Format console output with colors
   */
  formatConsoleOutput(level, message, data = null) {
    try {
      const color = this.getColor(level);
      const timestamp = this.getTimestamp();
      const levelStr = `[${level}]`.padEnd(7);

      // Convert message to string if it's an object - ensure it's always a string
      let messageStr = "";
      if (message === null) {
        messageStr = "null";
      } else if (message === undefined) {
        messageStr = "undefined";
      } else if (typeof message === "object") {
        try {
          messageStr = JSON.stringify(message, null, 2);
        } catch (e) {
          try {
            messageStr = String(message);
          } catch (stringError) {
            messageStr = "[Unable to convert to string]";
          }
        }
      } else {
        messageStr = String(message);
      }

      // Only print the message, not the data objects
      return `${COLORS.dim}${timestamp}${COLORS.reset} ${color}${levelStr}${COLORS.reset} ${messageStr}`;
    } catch (error) {
      // Last resort - return a safe string
      const errorMsg = error instanceof Error ? error.message : String(error);
      return `[LOGGER ERROR] ${errorMsg}`;
    }
  }

  /**
   * Check if log level should be logged
   */
  shouldLog(level) {
    return LOG_LEVELS[level] <= LOG_LEVELS[this.logLevel];
  }

  /**
   * Log error
   */
  error(message, error = null) {
    if (!this.shouldLog("ERROR")) return;

    const errorData = error
      ? {
          message: error.message,
          stack: error.stack,
          ...(error.code && { code: error.code }),
        }
      : null;

    // Handle message - if it's an object, convert to string
    let logMessage;
    let logData;

    if (typeof message === "object" && message !== null) {
      // Message is an object, use it as data
      logMessage = "Error occurred";
      logData = errorData || message;
    } else {
      // Message is a string
      logMessage = message || "Error occurred";
      logData = errorData;
    }

    // Only print message to console, data is still written to file
    console.error(this.formatConsoleOutput("ERROR", logMessage));
    if (logData) {
      // Also log error details to console if available
      if (errorData && errorData.message) {
        console.error(`  ${COLORS.dim}→${COLORS.reset} ${errorData.message}`);
      }
    }
    this.writeToFile("ERROR", logMessage, logData);
  }

  /**
   * Log warning
   */
  warn(message, data = null) {
    if (!this.shouldLog("WARN")) return;

    // Handle message - if it's an object, convert to string
    let logMessage = message;
    let logData = data;

    if (typeof message === "object" && message !== null && !data) {
      // Message is an object and no separate data provided
      logMessage = "Warning";
      logData = message;
    }

    // Only print message to console, data is still written to file
    console.warn(this.formatConsoleOutput("WARN", logMessage));
    this.writeToFile("WARN", logMessage, logData);
  }

  /**
   * Log info
   */
  info(message, data = null) {
    if (!this.shouldLog("INFO")) return;

    // Handle message - if it's an object, convert to string
    let logMessage = message;
    let logData = data;

    if (typeof message === "object" && message !== null && !data) {
      // Message is an object and no separate data provided
      logMessage = "Info";
      logData = message;
    }

    // Only print message to console, data is still written to file
    console.log(this.formatConsoleOutput("INFO", logMessage));
    this.writeToFile("INFO", logMessage, logData);
  }

  /**
   * Log debug
   */
  debug(message, data = null) {
    if (!this.shouldLog("DEBUG")) return;

    // Handle message - if it's an object, convert to string
    let logMessage = message;
    let logData = data;

    if (typeof message === "object" && message !== null && !data) {
      // Message is an object and no separate data provided
      logMessage = "Debug";
      logData = message;
    }

    // Only print message to console, data is still written to file
    console.log(this.formatConsoleOutput("DEBUG", logMessage));
    this.writeToFile("DEBUG", logMessage, logData);
  }

  /**
   * Log HTTP request (simplified)
   */
  logRequest(req, res, responseTime = null) {
    const { method, url } = req;
    const timestamp = this.getTimestamp();
    const responseTimeStr = responseTime ? `${responseTime}ms` : "";

    // Determine log level based on status code
    const statusCode = res.statusCode;
    let logLevel;
    let logLevelColor;
    if (statusCode >= 500) {
      logLevel = "ERROR";
      logLevelColor = COLORS.red;
    } else if (statusCode >= 400) {
      logLevel = "WARN";
      logLevelColor = COLORS.yellow;
    } else {
      logLevel = "INFO";
      logLevelColor = COLORS.green;
    }

    // Simple log format: [TIMESTAMP] [LOG_LEVEL] METHOD ROUTE TIME_TAKEN
    const logMessage = `${method} ${url} ${responseTimeStr}`.trim();

    // Format with colored log level
    const formattedMessage = `${COLORS.dim}[${timestamp}]${COLORS.reset} ${logLevelColor}[${logLevel}]${COLORS.reset} ${logMessage}`;

    // Log based on status code
    if (statusCode >= 500) {
      console.error(formattedMessage);
      this.writeToFile("ERROR", logMessage, {
        timestamp,
        statusCode,
        logLevel,
      });
    } else if (statusCode >= 400) {
      console.warn(formattedMessage);
      this.writeToFile("WARN", logMessage, { timestamp, statusCode, logLevel });
    } else {
      console.log(formattedMessage);
      this.writeToFile("INFO", logMessage, { timestamp, statusCode, logLevel });
    }
  }

  /**
   * Get color for HTTP status code
   */
  getStatusColor(statusCode) {
    if (statusCode >= 500) return COLORS.red;
    if (statusCode >= 400) return COLORS.yellow;
    if (statusCode >= 300) return COLORS.cyan;
    return COLORS.green;
  }

  /**
   * Sanitize sensitive data from objects
   */
  sanitizeData(data) {
    if (!data || typeof data !== "object") return data;

    const sensitiveFields = [
      "password",
      "token",
      "secret",
      "apiKey",
      "apikey",
      "authorization",
      "auth",
      "creditCard",
      "creditcard",
      "cvv",
      "ssn",
      "pin",
    ];

    const sanitized = Array.isArray(data) ? [...data] : { ...data };

    for (const key in sanitized) {
      const lowerKey = key.toLowerCase();
      if (sensitiveFields.some((field) => lowerKey.includes(field))) {
        sanitized[key] = "***REDACTED***";
      } else if (
        typeof sanitized[key] === "object" &&
        sanitized[key] !== null
      ) {
        sanitized[key] = this.sanitizeData(sanitized[key]);
      }
    }

    return sanitized;
  }
}

// Export singleton instance
export default new Logger();
