// receiptinterface.js - Thermal receipt printer interface implementation
import log4js from "log4js";
import { loadConfig } from "../../config.js";
import { ThermalPrinter, PrinterTypes } from "node-thermal-printer";
import { convertPDFToImage } from "../../pdfUtils.js";

const config = loadConfig();
const logger = log4js.getLogger("Receipt Printer Interface");
logger.level = config.logLevel;

export default class ReceiptInterface {
  constructor(documentPrinterClient) {
    this.documentPrinterClient = documentPrinterClient;
    this.host = documentPrinterClient.connection.host;
    this.name = documentPrinterClient.documentPrinter.name;
    this.port = documentPrinterClient.connection.port || 9100;
    this.interface =
      documentPrinterClient.connection.interface || "epsonReceipt";
    this.protocol = documentPrinterClient.connection.protocol;
    this.isConnected = false;
    this.receiptPrinter = null;
    this.retrieveStatusInterval = null;
    this.images = new Map();
  }

  buildPrinterUrl() {
    switch (this.protocol) {
      case "tcp":
        return `tcp://${this.host}:${this.port}`;
      case "system":
        return `printer:${this.host}`;
      case "serial":
        return this.host;
      default:
        logger.warn(`Unknown protocol ${this.protocol}, defaulting to tcp.`);
        return `tcp://${this.host}:${this.port}`;
    }
  }

  async connect() {
    try {
      // Determine printer type enum
      let type;
      switch (this.interface) {
        case "epsonReceipt":
          type = PrinterTypes.EPSON;
          break;
        case "starReceipt":
          type = PrinterTypes.STAR;
          break;
        default:
          type = PrinterTypes.EPSON;
          logger.warn(
            `Unknown interface ${this.interface}, defaulting to EPSON`
          );
      }

      // Determine interface based on connection type
      const interfaceStr = this.buildPrinterUrl();

      logger.info(
        `Connecting to receipt printer ${this.name} (${interfaceStr})`
      );

      // Initialize thermal printer
      this.receiptPrinter = new ThermalPrinter({
        type: type,
        interface: interfaceStr,
        options: {
          timeout: 10000,
        },
      });

      // Test connection
      const isConnected = await this.receiptPrinter.isPrinterConnected();
      if (!isConnected) {
        logger.error("Printer is not connected or not reachable");
        return { error: "Printer is not connected or not reachable." };
      }

      this.isConnected = true;
      logger.info(`Successfully connected to receipt printer ${this.name}`);
      return true;
    } catch (error) {
      logger.error(`Failed to connect to receipt printer ${this.name}:`, error);
      this.isConnected = false;
      return {
        error: "Failed to connect to receipt printer. " + error.message,
      };
    }
  }

  async disconnect() {
    logger.info(`Disconnecting from receipt printer ${this.name}`);
    this.isConnected = false;
    this.receiptPrinter = null;
    return { success: true };
  }

  async initialize() {
    logger.info(`Initializing receipt printer ${this.name}`);
    // Thermal printers typically don't need special initialization
    // but we can test the connection
    if (this.receiptPrinter) {
      try {
        const isConnected = await this.receiptPrinter.isPrinterConnected();
        if (!isConnected) {
          logger.error(
            `Printer not connected during initialization for receipt printer ${this.name}`
          );
          return { error: "Printer not connected during initialization" };
        }
        logger.info(`Receipt printer ${this.name} initialized successfully`);
      } catch (error) {
        logger.error(
          `Failed to initialize receipt printer ${this.name}:`,
          error
        );
      }
    }
    return true;
  }

  async print(jobId) {
    logger.info(`Printing job ${jobId} to receipt printer ${this.name}`);

    if (!this.isConnected || !this.receiptPrinter) {
      throw new Error("Printer is not connected");
    }

    try {
      // Clear the printer buffer to prevent previous print jobs from being included
      this.receiptPrinter.clear();

      const images = this.images.get(jobId);
      if (!images || (Array.isArray(images) && images.length === 0)) {
        throw new Error("Image not found");
      }

      // convertPDFToImage returns an array of buffers, so handle multiple pages
      const imageArray = Array.isArray(images) ? images : [images];

      for (const image of imageArray) {
        // Ensure the image is a proper Buffer and create a fresh copy
        // This prevents issues with pngjs reading from consumed streams
        // pdf-to-img returns PNG buffers, but we create a copy to avoid any stream issues
        const imageBuffer = Buffer.isBuffer(image)
          ? Buffer.from(image) // Create a fresh copy
          : Buffer.from(image); // Convert if needed
        this.receiptPrinter.printImageBuffer(imageBuffer);
        this.receiptPrinter.cut();
      }

      // Execute the print job
      await this.receiptPrinter.execute({ waitForResponse: true });

      logger.info(
        `Successfully printed job ${jobId} to receipt printer ${this.name}`
      );

      return { success: true };
    } catch (error) {
      logger.error(
        `Failed to print job ${jobId} to receipt printer ${this.name}:`,
        error
      );
      return { success: false, error: error.message };
    }
  }

  async deploy(documentJob, pdf) {
    logger.info(
      `Deploying job ${documentJob._id} to receipt printer ${this.name}`
    );

    const images = await convertPDFToImage(pdf, { width: 512 });
    // Store the array of image buffers
    this.images.set(documentJob._id, images);
    return { success: true };
  }

  async retrieveStatus() {
    logger.debug(`Getting status of receipt printer ${this.name}`);
    if (this.isOnline == false) {
      logger.error("Printer is not connected or not reachable");
      return { error: "Printer is not connected or not reachable." };
    }
    try {
      const status = await this.receiptPrinter.raw(
        Buffer.from([0x10, 0x04, 0x04])
      );
      logger.info(`Printer status: ${status}`);
    } catch (error) {
      logger.error(`Failed to execute printer status:`, error);
    }

    logger.debug(`Receipt printer ${this.name} is connected.`);
    return true;
  }
}
