// cupsprinterinterface.js - CUPS printer interface implementation
import log4js from "log4js";
import { loadConfig } from "../../config.js";
import ipp from "ipp";
import { startWaiting, stopWaiting } from "../../spinner.js";

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

export default class CupsInterface {
  constructor(documentPrinterClient) {
    this.documentPrinterClient = documentPrinterClient;
    this.host = documentPrinterClient.connection.host;
    this.port = documentPrinterClient.connection.port || 631;
    this.interface = documentPrinterClient.connection.interface || "cups";
    this.protocol = documentPrinterClient.connection.protocol;
    this.name = documentPrinterClient.documentPrinter.name;
    this.isConnected = false;
    this.cupsPrinter = null;
    this.pdfs = new Map();
  }

  /**
   * Build the IPP printer URL from connection settings
   */
  buildPrinterUrl() {
    var hostWithPort = `${this.host}:${this.port}`;
    var path = "/";
    if (this.host.includes("/")) {
      const parts = this.host.split("/");
      var hostName = parts[0];
      if (hostName.includes(":")) {
        const portHost = hostName.split(":");
        hostName = portHost[0];
      }
      hostWithPort = `${hostName}:${this.port}`;
      path += parts.slice(1).join("/");
    }
    switch (this.protocol) {
      case "ipp":
        return `ipp://${hostWithPort}${path}`;
      case "http":
        return `http://${hostWithPort}${path}`;
      default:
        logger.warn(`Unknown protocol ${this.protocol}, defaulting to ipp.`);
        return `ipp://${hostWithPort}${path}`;
    }
  }

  /**
   * Promisify IPP execute method
   */
  async executeIPP(operation, message) {
    return new Promise((resolve, reject) => {
      this.cupsPrinter.execute(operation, message, (err, res) => {
        if (err) {
          reject(err);
        } else {
          resolve(res);
        }
      });
    });
  }

  async connect() {
    logger.info(`Connecting to CUPS printer ${this.name}`);

    try {
      this.cupsPrinterUrl = this.buildPrinterUrl();
      logger.debug(`Printer URL: ${this.cupsPrinterUrl}`);

      // Create IPP printer instance
      this.cupsPrinter = ipp.Printer(this.cupsPrinterUrl);

      // Test connection by getting printer attributes
      const getPrinterAttributes = {
        "operation-attributes-tag": {
          "requested-attributes": [
            "printer-name",
            "printer-state",
            "printer-state-message",
            "printer-uri-supported",
          ],
        },
      };

      try {
        const response = await this.executeIPP(
          "Get-Printer-Attributes",
          getPrinterAttributes
        );

        const printerState =
          response["printer-attributes-tag"]?.["printer-state"];
        logger.info(
          `Successfully connected to CUPS printer ${this.name}. State: ${printerState}`
        );

        this.isConnected = true;

        return true;
      } catch (error) {
        logger.error(
          `Failed to get printer attributes for ${this.name}:`,
          error
        );
        return {
          error: "Failed to get printer attributes. " + error.message,
        };
      }
    } catch (error) {
      logger.error(`Failed to connect to CUPS printer:`, error);
      this.isConnected = false;
      this.cupsPrinter = null;
      return {
        error: "Failed to connect to CUPS printer. " + error.message,
      };
    }
  }

  async disconnect() {
    startWaiting("Disconnecting from CUPS printer...", logger);
    this.isConnected = false;
    this.cupsPrinter = null;
    await stopWaiting();
    return { success: true };
  }

  async initialize() {
    startWaiting("Initializing CUPS printer...", logger);

    if (!this.isConnected || !this.cupsPrinter) {
      await stopWaiting();
      return { error: "Printer is not connected" };
    }

    try {
      // Verify printer is ready by checking attributes
      const getPrinterAttributes = {
        "operation-attributes-tag": {
          "requested-attributes": [
            "printer-state",
            "printer-state-message",
            "printer-state-reasons",
          ],
        },
      };

      const response = await this.executeIPP(
        "Get-Printer-Attributes",
        getPrinterAttributes
      );

      const printerState =
        response["printer-attributes-tag"]?.["printer-state"];
      const stateMessage =
        response["printer-attributes-tag"]?.["printer-state-message"] || "";

      // Printer states: 3 = idle, 4 = processing, 5 = stopped
      if (printerState === 5) {
        logger.warn(`Printer ${this.name} is stopped: ${stateMessage}`);
        await stopWaiting();
        return { error: `Printer is stopped: ${stateMessage}` };
      }

      logger.info(
        `CUPS printer ${this.name} initialized successfully. State: ${printerState}`
      );
      await stopWaiting();
      return true;
    } catch (error) {
      await stopWaiting();
      logger.error(`Failed to initialize CUPS printer ${this.name}:`, error);
      return { error: error.message || "Failed to initialize CUPS printer" };
    }
  }

  /**
   * Get document data from documentJob
   * Supports file references, buffers, and content
   */
  async getDocumentData(documentJob) {
    let data = null;

    // If documentJob has a file reference, fetch it
    if (documentJob.file || documentJob.fileId) {
      const fileId =
        documentJob.file?._id || documentJob.file || documentJob.fileId;
      if (this.documentPrinterClient.socketClient?.fileManager) {
        data =
          await this.documentPrinterClient.socketClient.fileManager.getFile(
            fileId
          );
      } else {
        throw new Error("File manager not available to fetch file");
      }
    }
    // If documentJob has a buffer directly
    else if (documentJob.buffer || documentJob.data) {
      data = documentJob.buffer || documentJob.data;
    }
    // If documentJob has rendered image
    else if (documentJob.renderedImage) {
      data = documentJob.renderedImage;
    }
    // If documentJob has content (text), convert to buffer
    else if (documentJob.content) {
      data = Buffer.from(documentJob.content, "utf-8");
    } else {
      throw new Error("Document job has no printable content");
    }

    // Ensure data is a Buffer
    if (!Buffer.isBuffer(data)) {
      if (data instanceof Uint8Array) {
        data = Buffer.from(data);
      } else if (data instanceof ArrayBuffer) {
        data = Buffer.from(data);
      } else if (typeof data === "string") {
        data = Buffer.from(data, "utf-8");
      } else {
        throw new Error(
          "Document data must be a Buffer, Uint8Array, ArrayBuffer, or string"
        );
      }
    }

    return data;
  }

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

    // Store the PDF buffer
    this.pdfs.set(documentJob._id, pdf);
    return { success: true };
  }

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

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

    const pdf = this.pdfs.get(jobId);
    if (!pdf) {
      throw new Error("PDF not found for job");
    }

    try {
      // Ensure PDF is a Buffer
      let documentData = pdf;
      if (!Buffer.isBuffer(documentData)) {
        if (documentData instanceof Uint8Array) {
          documentData = Buffer.from(documentData);
        } else if (documentData instanceof ArrayBuffer) {
          documentData = Buffer.from(documentData);
        } else if (typeof documentData === "string") {
          documentData = Buffer.from(documentData, "utf-8");
        } else {
          throw new Error(
            "PDF data must be a Buffer, Uint8Array, ArrayBuffer, or string"
          );
        }
      }

      // Build IPP print job message
      const jobName = jobId?.toString() || "Print Job";
      const userName = process.env.USER || "system";

      const printJobMessage = {
        "operation-attributes-tag": {
          "requesting-user-name": userName,
          "job-name": jobName,
          "document-format": "application/pdf",
        },
        data: documentData,
      };

      // Send print job
      logger.debug(`Sending print job ${jobId} to ${this.cupsPrinterUrl}`);
      const response = await this.executeIPP("Print-Job", printJobMessage);

      // Extract job ID from response
      const ippJobId = response["job-attributes-tag"]?.["job-id"];
      const jobUri = response["job-attributes-tag"]?.["job-uri"];

      logger.info(
        `Successfully printed job ${jobId} to CUPS printer ${this.name}. IPP Job ID: ${ippJobId}`
      );

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