// documentprinterclient.js - Handles connection to a single document printer
import { loadConfig } from "../config.js";
import log4js from "log4js";
import { startWaiting, stopWaiting } from "../spinner.js";
import CupsPrinterInterface from "./interfaces/cupsinterface.js";
import ReceiptInterface from "./interfaces/receiptinterface.js";
import { sendIPC } from "../electron/ipc.js";
// Load configuration
const config = loadConfig();

const logger = log4js.getLogger("Document Printer Client");
logger.level = config.logLevel;

export class DocumentPrinterClient {
  constructor(
    documentPrinter = {
      connection: { interface: "cups", host: "localhost", port: 9100 },
    },
    documentPrinterManager,
  ) {
    this.id = documentPrinter._id;
    this.documentPrinter = documentPrinter;
    this.connection = documentPrinter.connection;
    this.queue = [];
    this.documentPrinterManager = documentPrinterManager;
    this.currentJob = null;
    this.active = documentPrinter.active == true;
    this.socketClient = documentPrinterManager.socketClient;
    this.interface = documentPrinter.connection.interface || "cups"; // cups, receipt, or os
    this.state = { type: this.active == true ? "offline" : "inactive" };
    this.isOnline = documentPrinter.online || false;
    this.shouldReconnect = true;
    this.isProcessingQueue = false;
    this.eventUpdateInterval = null;
    this.initializeInterface();
    this.registerEventHandlers();
    this.subscribeToActions();
    this.subscribeToObjectUpdates();
  }

  initializeInterface() {
    logger.info(
      `Initializing ${this.interface} interface for document printer ${this.id}`,
    );
    switch (this.interface) {
      case "cups":
        this.printerInterface = new CupsPrinterInterface(this);
        logger.debug("Cups printer interface initialized");
        break;
      case "epsonReceipt":
        this.printerInterface = new ReceiptInterface(this);
        logger.debug("Epson receipt printer interface initialized");
        break;
      case "starReceipt":
        this.printerInterface = new ReceiptInterface(this);
        logger.debug("Star receipt printer interface initialized");
        break;
      default:
        logger.error(`Unknown interface type: ${this.interface}`);
        this.printerInterface = null;
    }
  }

  async updateDocumentPrinter(data) {
    logger.debug(`Updating document printer ${this.id} with data...`);
    sendIPC("setDocumentPrinter", { _id: this.id, ...data });

    // Check for connection changes before updating

    if (data?.connection) {
      const oldConnection = this.connection || {};
      const newConnection = data?.connection || {};

      const hostChanged =
        newConnection.host != null && newConnection.host !== oldConnection.host;
      const protocolChanged =
        newConnection.protocol != null &&
        newConnection.protocol !== oldConnection.protocol;
      const interfaceChanged =
        newConnection.interface != null &&
        newConnection.interface !== oldConnection.interface;
      const portChanged = newConnection?.port !== oldConnection?.port;

      const connectionChanged =
        hostChanged || interfaceChanged || portChanged || protocolChanged;

      logger.debug(`Connection changed: ${connectionChanged}`);
      logger.debug(`Host changed: ${hostChanged}`);
      logger.debug(`Interface changed: ${interfaceChanged}`);
      logger.debug(`Port changed: ${portChanged}`);
      logger.debug(`Protocol changed: ${protocolChanged}`);

      // Update document printer data
      this.documentPrinter = { ...this.documentPrinter, ...data };

      // Update interface if it changed
      if (interfaceChanged) {
        this.interface = newConnection.interface;
      }

      // Re-initialize only if connection properties changed
      if (connectionChanged) {
        this.connection = newConnection;
        await this.printerInterface.disconnect();
        this.initializeInterface();
        await this.reconnect();
      }
    }

    this.documentPrinter = { ...this.documentPrinter, ...data };

    if (Object.hasOwn(data || {}, "active") && data.active != this.active) {
      if (data.active == true) {
        await this.setActive();
      } else {
        await this.setInactive();
      }
    }
  }

  registerEventHandlers() {
    // Register event handlers for document printer notifications
    // This can be extended as needed
  }

  subscribeToActions() {
    this.socketClient.subscribeToObjectActions({
      objectType: "documentPrinter",
      _id: this.id,
    });
  }

  subscribeToObjectUpdates() {
    this.socketClient.subscribeToObjectUpdates({
      objectType: "documentPrinter",
      _id: this.id,
    });
  }

  async connect() {
    if (this.active == false) {
      this.shouldReconnect = false;
      this.isOnline = false;
      this.state = { type: "inactive" };
      await this.updateDocumentPrinterState();
      return false;
    }
    logger.info(
      `Connecting to document printer ${this.id} (${this.interface})`,
    );

    clearTimeout(this.reconnectTimeout);
    // Always stop the event update interval when connecting
    clearInterval(this.eventUpdateInterval);
    this.eventUpdateInterval = null;
    this.state = { type: "connecting", message: null };
    this.isOnline = false;
    await this.updateDocumentPrinterState();

    if (!this.printerInterface) {
      logger.error(
        `Cannot connect: No interface initialized for ${this.interface}`,
      );
      return false;
    }

    const result = await this.printerInterface.connect();

    if (result.error) {
      logger.error(
        `Error connecting to document printer ${this.documentPrinter.name}:`,
        result.error,
      );
      this.isOnline = false;
      this.state = { type: "offline", message: result.error };
      await this.updateDocumentPrinterState();
      return false;
    }
    logger.info(
      `Connected to document printer ${this.documentPrinter.name} (${this.interface})`,
    );
    return true;
  }

  async reconnect() {
    if (this.active == false) {
      logger.info(
        `Document printer ${this.documentPrinter.name} is inactive, skipping reconnect`,
      );
      this.shouldReconnect = false;
      this.isOnline = false;
      this.state = { type: "inactive" };
      await this.updateDocumentPrinterState();
      return false;
    }
    if (this.isOnline == true) {
      logger.info(
        `Disconnecting from document printer ${this.documentPrinter.name} before reconnecting...`,
      );
      await this.disconnect();
    }
    logger.info(
      `Reconnecting to document printer ${this.documentPrinter.name}`,
    );
    this.shouldReconnect = true;
    const connectResult = await this.connect();
    if (connectResult == false) {
      logger.error(
        `Error reconnecting to document printer ${this.documentPrinter.name}:`,
        connectResult.error,
      );
      if (this.shouldReconnect) {
        // Attempt to reconnect after delay
        setTimeout(() => this.reconnect(), 30000);
      }
      return false;
    }
    const initializeResult = await this.initialize();
    if (initializeResult == false) {
      logger.error(
        `Error initializing document printer ${this.documentPrinter.name}:`,
        initializeResult.error,
      );
      if (this.shouldReconnect) {
        // Attempt to reconnect after delay
        setTimeout(() => this.reconnect(), 30000);
      }
      return false;
    }

    return true;
  }

  async initialize() {
    startWaiting("Running document printer initialization...", logger);
    try {
      this.state = { type: "initializing", message: null };
      this.isOnline = true;
      this.connectedAt = new Date();
      await this.updateDocumentPrinterState();
      if (this.printerInterface && this.printerInterface.initialize) {
        const result = await this.printerInterface.initialize();
        if (result.error) {
          logger.error(
            `Error initializing document printer ${this.documentPrinter.name}:`,
            result.error,
          );
          this.state = { type: "offline", message: result.error };
          await this.updateDocumentPrinterState();
          await stopWaiting();
          return false;
        }
        this.state = { type: "standby", message: null };
        await this.updateDocumentPrinterState();
        this.eventUpdateInterval = setInterval(
          this.handleEventUpdate.bind(this),
          3000,
        );
        await stopWaiting();
        return true;
      }
      await stopWaiting();
    } catch (error) {
      await stopWaiting();
      throw error;
    }
  }

  async handleEventUpdate() {
    if (this.printerInterface && this.printerInterface.retrieveStatus) {
      try {
        //await this.printerInterface.retrieveStatus();
      } catch (error) {
        logger.error(
          `Error retrieving status for document printer ${this.documentPrinter.name}:`,
          error,
        );
      }
    }
  }

  async updateDocumentPrinterState() {
    try {
      // Update state in database or via socket client
      // This can be implemented based on your database structure
      this.socketClient.editObject({
        _id: this.id,
        objectType: "documentPrinter",
        updateData: {
          online: this.isOnline,
          state: this.state,
          connectedAt: this.connectedAt,
        },
      });
    } catch (error) {
      logger.error(`Failed to update document printer state:`, error);
    }
  }

  async updateJobState(jobId, state) {
    logger.info(`Updating job state for ${jobId}`);
    await this.socketClient.editObject({
      _id: jobId,
      objectType: "documentJob",
      updateData: { state: state },
    });
    logger.info(`Updated job state for ${jobId}:`, state);
  }

  async deployDocumentJob(documentJob) {
    logger.info(
      `Deploying document job ${documentJob._id} to ${this.documentPrinter.name}`,
    );

    if (!this.isOnline) {
      logger.error(
        `Cannot deploy job: Document printer not connected (${this.documentPrinter.name})`,
      );
      return { error: "Document printer not connected" };
    }

    if (!this.printerInterface) {
      logger.error(
        `Cannot deploy job: No interface initialized (${this.documentPrinter.name})`,
      );
      return { error: "No interface initialized" };
    }

    try {
      if (this.printerInterface.deploy) {
        await this.updateJobState(documentJob._id, { type: "deploying" });
        const documentTemplate = await this.socketClient.getObject({
          objectType: "documentTemplate",
          _id: documentJob.documentTemplate._id,
        });
        await this.updateJobState(documentJob._id, {
          type: "deploying",
          progress: 0.25,
        });
        const object = await this.socketClient.getObject({
          objectType: documentJob.objectType,
          _id: documentJob.object._id,
        });
        await this.updateJobState(documentJob._id, {
          type: "deploying",
          progress: 0.5,
        });
        if (!documentTemplate) {
          logger.error(
            `Document template not found for job ${documentJob._id}`,
          );
          return { error: "Document template not found" };
        }
        const pdfObj = await this.socketClient.renderTemplatePDF({
          _id: documentTemplate._id,
          content: documentTemplate.content,
          object: object,
        });
        await this.updateJobState(documentJob._id, {
          type: "deploying",
          progress: 0.75,
        });
        if (!pdfObj) {
          logger.error(
            `Failed to render document template for job ${documentJob._id}`,
          );
          return { error: "Failed to render document template" };
        }
        const result = await this.printerInterface.deploy(
          documentJob,
          pdfObj.pdf,
        );
        await this.updateJobState(documentJob._id, {
          type: "deploying",
          progress: 1.0,
        });
        logger.info(
          `Deployed document job ${documentJob._id} to ${this.documentPrinter.name}`,
        );
        await this.updateJobState(documentJob._id, {
          type: "queued",
          progress: null,
        });
        // Only add job to queue if it's not already there
        if (!this.queue.includes(documentJob._id)) {
          this.queue.push(documentJob._id);
        } else {
          logger.warn(
            `Job ${documentJob._id} is already in the queue for ${this.documentPrinter.name}`,
          );
        }
        this.startQueue();
        return result;
      } else {
        logger.error(
          `Interface ${this.interface} does not support deployDocumentJob`,
        );
        return { error: "Interface does not support this operation" };
      }
    } catch (error) {
      logger.error(
        `Error deploying document job to ${this.documentPrinter.name}:`,
        error,
      );
      await this.updateJobState(documentJob._id, {
        type: "error",
        progress: null,
        message: error.message || "Failed to deploy document job",
      });
      return { error: error.message || "Failed to deploy document job" };
    }
  }

  async startQueue() {
    if (!this.isOnline) {
      logger.error(
        `Cannot start queue: Document printer not connected (${this.documentPrinter.name})`,
      );
      return { error: "Document printer not connected" };
    }
    if (!this.printerInterface) {
      logger.error(
        `Cannot start queue: No interface initialized (${this.documentPrinter.name})`,
      );
      return { error: "No interface initialized" };
    }
    // Prevent concurrent queue processing
    if (this.isProcessingQueue) {
      logger.debug(
        `Queue is already being processed for ${this.documentPrinter.name}`,
      );
      return { info: "Queue is already being processed" };
    }
    if (this.state.type == "standby") {
      logger.info(`Starting queue for ${this.documentPrinter.name}`);

      await this.runQueue();
    }
  }

  async runQueue() {
    if (!this.isOnline) {
      logger.error(
        `Cannot print next job: Document printer not connected (${this.documentPrinter.name})`,
      );
      return { error: "Document printer not connected" };
    }
    if (!this.printerInterface) {
      logger.error(
        `Cannot print next job: No interface initialized (${this.documentPrinter.name})`,
      );
      return { error: "No interface initialized" };
    }
    if (this.state.type != "standby") {
      logger.error(
        `Cannot print next job: Document printer not in standby mode (${this.documentPrinter.name})`,
      );
      return { error: "Document printer not in standby mode" };
    }

    // Prevent concurrent queue processing
    if (this.isProcessingQueue) {
      logger.debug(
        `Queue is already being processed for ${this.documentPrinter.name}`,
      );
      return { info: "Queue is already being processed" };
    }

    this.isProcessingQueue = true;
    logger.info(`Starting to print jobs for ${this.documentPrinter.name}`);
    this.state = { type: "printing", message: null };
    await this.updateDocumentPrinterState();
    try {
      // Process all jobs in the queue using a loop instead of recursion
      while (this.queue.length > 0) {
        // Re-check connection status before each job
        if (!this.isOnline) {
          logger.error(
            `Printer went offline while printing (${this.documentPrinter.name})`,
          );
          this.state = {
            type: "offline",
            message: "Connection lost during printing",
          };
          await this.updateDocumentPrinterState();
          return { error: "Document printer not connected" };
        }
        if (!this.printerInterface) {
          logger.error(
            `Printer interface lost while printing (${this.documentPrinter.name})`,
          );
          this.state = {
            type: "offline",
            message: "Interface lost during printing",
          };
          await this.updateDocumentPrinterState();
          return { error: "No interface initialized" };
        }

        // Get the next job ID but don't remove it yet
        const jobId = this.queue[0];
        if (!jobId) {
          break;
        }

        try {
          logger.info(`Printing job ${jobId} for ${this.documentPrinter.name}`);
          await this.updateJobState(jobId, { type: "printing" });

          await this.printerInterface.print(jobId);
          logger.info(
            `Successfully printed job ${jobId} for ${this.documentPrinter.name}`,
          );
          // Only remove job from queue after successful printing
          this.queue.shift();
          await this.updateJobState(jobId, { type: "complete" });
        } catch (error) {
          logger.error(
            `Error printing job ${jobId} for ${this.documentPrinter.name}:`,
            error,
          );

          // Remove failed job from queue to prevent infinite retry loop
          // You may want to implement retry logic or error handling here
          this.queue.shift();
          await this.updateJobState(jobId, { type: "failed" });
          // Continue with next job even if one fails
        }
      }

      logger.info(
        `Finished printing all jobs for ${this.documentPrinter.name}`,
      );
      this.state = { type: "standby", message: null };
      await this.updateDocumentPrinterState();
      return true;
    } finally {
      // Always reset the processing flag, even if there was an error
      this.isProcessingQueue = false;
    }
  }

  async disconnect() {
    logger.info(`Disconnecting from ${this.documentPrinter.name}`);
    this.shouldReconnect = false;
    // Always stop the event update interval when disconnecting
    clearInterval(this.eventUpdateInterval);
    this.eventUpdateInterval = null;
    if (this.printerInterface && this.printerInterface.disconnect) {
      await this.printerInterface.disconnect();
    }
    this.isOnline = false;
    this.state = { type: this.active == false ? "inactive" : "offline" };
    this.isProcessingQueue = false;
    this.queue = []; // Clear queue on disconnect
    await this.updateDocumentPrinterState();
    clearTimeout(this.reconnectTimeout);
    logger.info(`Successfully disconnected from ${this.documentPrinter.name}`);
    return true;
  }

  async setInactive() {
    this.active = false;
    this.documentPrinter.active = false;
    this.isOnline = false;
    await this.disconnect();
    this.state = { type: "inactive" };
    await this.updateDocumentPrinterState();
  }

  async setActive() {
    this.active = true;
    this.documentPrinter.active = true;
    this.shouldReconnect = true;
    this.isOnline = false;
    await this.reconnect();
  }
}
