// ecservice.js
const WebSocket = require("ws");
const config = require("config");
const { exec } = require("child_process");
const ThermalPrinter = require("node-thermal-printer").printer;
const PrinterTypes = require("node-thermal-printer").types;
// const printer = require('@thiagoelg/node-printer');
const axios = require("axios");
const {
  createServiceUtils,
  formatDateTime,
  summarizeAction,
} = require("./src/utils/ecservice-utils");
const {
  isDebugPreviewEnabled,
  logPrintPreview,
} = require("./src/utils/print-preview");
const {
  buildBillTemplate,
} = require("./config/print-templates/bill");
const {
  buildComandaTemplate,
} = require("./config/print-templates/comanda");
const {
  buildFollowedTemplate,
} = require("./config/print-templates/followed");
const {
  buildPreviewTemplate,
} = require("./config/print-templates/preview");

// --- Global configuration vars ---
/**
 * @typedef {Object} Setting
 * @property {number} id
 * @property {string} key
 * @property {string} value
 * @property {string|null} group
 * @property {string|null} description
 * @property {boolean} is_enable
 * @property {string} created_at  // ISO date string
 * @property {string} updated_at  // ISO date string
 */

/** @type {Setting|null} */
let pos_settings = [];
let lastInitConfigSignature = "";
const { logCommand, logEvent, logPreview, logPrintSettings } =
  createServiceUtils(__dirname);

// Log running service name and version from package.json
try {
  const pkg = require("./package.json");
  const startMsg = `Starting ${pkg.name} v${pkg.version}`;
  logEvent("INFO", startMsg);
  // Also print immediately to console for quick access
  // console.log(startMsg);
} catch (err) {
  const warnMsg = "Unable to read package.json for version info";
  logEvent("WARN", warnMsg, {
    error: err.message || err,
  });
  console.warn(warnMsg, err.message || err);
}

// --- Create WebSocket server ---
const wss = new WebSocket.Server({
  port: 5000,
  path: "/Receive",
});

wss.on("listening", () => {
  logEvent("INFO", "WebSocket server started", {
    port: 5000,
    path: "/Receive",
  });
});
wss.on("error", (err) => {
  if (err.code === "EADDRINUSE") {
    logEvent("ERROR", "Port 5000 already in use");
    process.exit(1);
  } else {
    logEvent("ERROR", "WebSocket server error", { error: err.message || err });
  }
});
wss.on("connection", (ws) => {
  logEvent("INFO", "POS client connected", { version: config.get("version") });

  // Send welcome message
  ws.send(JSON.stringify({ message: "Welcome to EasyComande POS WebSocket!" }));

  // Listen for incoming messages
  ws.on("message", async (msg) => {
    try {
      const data = JSON.parse(msg);
      const actionMeta = summarizeAction(data);
      logEvent("INFO", "Incoming action", actionMeta);
      // DEBUG
      // data.debug = true;

      // TODO: implement your custom logic for printing, orders, etc.
      if (data.action == "init-configurations") {
        pos_settings = data.settings || [];
        logInitConfigurations(pos_settings.length);

        broadcast({
          action: data.action,
          message: "Configuration initialized successfully.",
        });
      } else if (data.action === "print") {
        logCommand("print", actionMeta);
        await handlePrint(data);
        broadcast({
          action: data.action,
          message: "Print command executed successfully.",
        });
      } else if (data.action === "ping") {
        logCommand("ping", actionMeta);
        broadcast({
          action: data.action,
          message: "Ping received.",
        });
      } else if (data.action === "order-detail") {
        logCommand("order-detail", actionMeta);
        broadcast({
          action: data.action,
          message: "Order detail received.",
        });
      } else if (data.action === "followed-print") {
        logCommand("followed-print", actionMeta);
        printTemplateFollowed(data.order, ws, data.debug);
        broadcast({
          action: data.action,
          message: "Followed-print command executed successfully.",
        });
      } else if (data.action === "comanda-print") {
        logCommand("comanda-print", actionMeta);
        printTemplateComanda(data.order, ws, data.debug, data.printerIp);
        broadcast({
          action: data.action,
          message: "Comanda-print command executed successfully.",
        });
      } else if (data.action === "preview-print") {
        logCommand("preview-print", actionMeta);
        printTemplatePreview(data.order, ws, data.debug);
        broadcast({
          action: data.action,
          message: "Preview-print command executed successfully.",
        });
      } else if (data.action === "bill-print") {
        logCommand("bill-print", actionMeta);
        printTemplateBill(data.order, ws, data.debug);
        broadcast({
          action: data.action,
          message: "Bill-print command executed successfully.",
        });
      }
      // Echo message back to all clients
      // broadcast({ message: data });
    } catch (err) {
      logEvent("ERROR", "Error processing incoming message", {
        error: err.message || err,
      });
      broadcast({
        action: "error",
        message: "Error processing the command: " + err.message,
      });
    }
  });

  ws.on("close", () => {
    logEvent("INFO", "POS client disconnected");
  });

  ws.on("error", (err) => {
    logEvent("ERROR", "WebSocket connection error", {
      error: err.message || err,
    });
  });
});

// --- Broadcast to all clients ---
function broadcast(response) {
  const msg = JSON.stringify(response);
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(msg);
    }
  });
}

// --- Example print handler ---
async function handlePrint(data) {
  try {
    // Example: log the print command
    logCommand("print", summarizeAction(data));

    // TODO: Use ThermalPrinter / printer module here
    logEvent("INFO", "Printing order", summarizeAction(data));
  } catch (err) {
    logEvent("ERROR", "Print error", { error: err.message || err });
  }
}

/**
 * Get a setting value by its key
 * @param {string} key
 * @returns {string|null}
 */
function getSettingValue(key) {
  if (!Array.isArray(pos_settings)) {
    return null;
  }
  const setting = pos_settings.find((s) => s.key === key);
  return setting ? setting.value : null;
}

/**
 * Get a full Setting object by its key
 * @param {string} key
 * @returns {Setting|null}
 */
function getSettingByKey(key) {
  if (!Array.isArray(pos_settings)) {
    logEvent("WARN", "pos_settings is not initialized yet");
    return null;
  }
  const setting = pos_settings.find((s) => s.key === key);
  return setting || null;
}

async function printTemplateComanda(order, ws, debug, printerIp = null) {
  const { output, printableOutput } = buildComandaTemplate(order);
  const debugEnabled = isDebugPreviewEnabled(debug);

  const print_directly_comande = getSettingValue("print_directly_comande");
  if (!print_directly_comande) {
    logEvent(
      "WARN",
      "Setting 'print_directly_comande' not found. Skipping comanda print",
    );
    return;
  }
  // console.log('printerIp', printerIp);
  // console.log('print_directly_comande', print_directly_comande);

  // L'intestazione deve essere diversa come font per la stampa
  if (debugEnabled) {
    // For HTML preview
    const finalHtml = `<pre class="printComandaContent">${printableOutput}</pre>`;
    logEvent("INFO", "Comanda debug preview generated", {
      orderId: order.id,
      table: order.table?.name,
    });
    logPrintPreview(
      logPreview,
      "comanda-print",
      printableOutput,
      order,
      "debug",
      printerIp || "configured",
    );
    return finalHtml;
  } else {
    if (printerIp && printerIp !== null) {
      return await printComandaToSinglePrinter(
        printerIp,
        order,
        ws,
        output,
        printableOutput,
      );
    }
    // Otherwise use setting print_directly_comande
    // Can be: "192.168.1.23" or "192.168.1.23;192.168.1.44"
    const printerIps = print_directly_comande
      .split(";")
      .map((ip) => ip.trim())
      .filter((ip) => ip.length > 0);

    // Print to all printers in list
    for (const ip of printerIps) {
      await printComandaToSinglePrinter(ip, order, ws, output, printableOutput);
    }
    return;
  }
}
async function printComandaToSinglePrinter(
  ip,
  order,
  ws,
  output,
  printableOutput,
) {
  let printer = initPrinter(ip, "comanda");
  let isConnected = await printer.isPrinterConnected();

  if (!isConnected) {
    const msg = `Printer ${ip} connection failed`;
    logEvent("ERROR", msg, { printer: ip, type: "comanda" });
    logPrintPreview(
      logPreview,
      "comanda-print",
      printableOutput,
      order,
      "printer-offline",
      ip,
    );
    ws.send(JSON.stringify({ message: msg, printer: ip }));
    return;
  }

  logEvent("INFO", "Printer connected", { printer: ip, type: "comanda" });

  // ---- PRINT START ----
  printer.alignLeft();
  printer.println("=== COMANDA ===");
  printer.newLine();

  printer.println(`TAVOLO: ${order.table?.name || ""}`);
  printer.println(`OSPITI: ${order.guests}`);
  printer.println(`CAMERIERE: ${order.waiter?.name || ""}`);
  printer.println(`DATA: ${formatDateTime(order.order_date)}`);
  printer.newLine();

  printer.setTextDoubleHeight();
  printer.setTextDoubleWidth();

  // Print line-by-line content
  output.split("\n").forEach((line) => printer.println(line));

  executePrinter(printer);
}
async function printTemplatePreview(order, ws, debug) {
  const lineWidth = config.get("customer.configurations.printer.lineWidth"); // 48
  const descWidth = 31; // width for DESCRIZIONE
  const priceWidth = 8; // width for PREZZO
  const totalWidth = lineWidth - descWidth - priceWidth; // remaining for TOTALE
  const debugEnabled = isDebugPreviewEnabled(debug);

  // Settings
  const setting_print_title = getSettingValue("print_title");
  const setting_order_coveredenable = getSettingValue("order_coveredenable");
  const setting_order_serviceenable = getSettingValue("order_serviceenable");
  const setting_print_directly_bill = getSettingValue("print_directly_bill");
  const setting_print_date = getSettingValue("print_date");

  logPrintSettings("preview", {
    print_title: setting_print_title,
    print_date: setting_print_date,
    order_coveredenable: setting_order_coveredenable,
    order_serviceenable: setting_order_serviceenable,
    print_directly_bill: setting_print_directly_bill,
  });
  const labelWidth = config.get("customer.configurations.printer.labelWidth");
  const valueWidth = config.get("customer.configurations.printer.valueWidth");
  const { output, footer, printableOutput } = buildPreviewTemplate({
    order,
    lineWidth,
    descWidth,
    priceWidth,
    totalWidth,
    labelWidth,
    valueWidth,
    settings: {
      order_coveredenable: setting_order_coveredenable,
      order_serviceenable: setting_order_serviceenable,
      print_date: setting_print_date,
    },
  });

  // -----------------------------
  // 🧩 DEBUG MODE
  // -----------------------------
  if (debugEnabled) {
    const finalHtml = `<pre class="printBillContent">${output} ${footer}</pre>`;
    logEvent("INFO", "Preview debug preview generated", {
      orderId: order.id,
      table: order.table?.name,
    });
    logPrintPreview(
      logPreview,
      "preview-print",
      printableOutput,
      order,
      "debug",
      setting_print_directly_bill || "configured",
    );
    return finalHtml;
  } else {
    if (!setting_print_directly_bill) {
      logEvent(
        "WARN",
        "Setting 'print_directly_bill' not found. Skipping preview print",
      );
      return;
    }

    // Prepare printer
    const printer = initPrinter(setting_print_directly_bill, "bill");
    let isConnected = await printer.isPrinterConnected();

    // -----------------------------
    // 🖨️ REAL PRINTER MODE
    // -----------------------------
    if (isConnected) {
      logEvent("INFO", "Printer connected", {
        printer: setting_print_directly_bill,
        type: "preview",
      });

      printer.alignCenter();
      printer.setTextDoubleHeight();
      printer.setTextDoubleWidth();
      if (setting_print_title) printer.println(setting_print_title);
      printer.newLine();
      printer.alignLeft();
      printer.setTextNormal();

      // Print bill lines from output
      output.split("\n").forEach((line) => {
        printer.println(line);
      });
      printer.alignCenter();
      footer.split("\n").forEach((line) => {
        printer.println(line);
      });
      executePrinter(printer);
    } else {
      logEvent("ERROR", "Printer network connection failed", {
        printer: setting_print_directly_bill,
        type: "preview",
      });
      logPrintPreview(
        logPreview,
        "preview-print",
        printableOutput,
        order,
        "printer-offline",
        setting_print_directly_bill,
      );
      ws.send("connectionError");
    }
  }
}
async function printTemplateBill(order, ws, debug) {
  const lineWidth = config.get("customer.configurations.printer.lineWidth"); // 48
  const descWidth = 31; // width for DESCRIZIONE
  const priceWidth = 8; // width for PREZZO
  const totalWidth = lineWidth - descWidth - priceWidth; // remaining for TOTALE
  const debugEnabled = isDebugPreviewEnabled(debug);

  // Settings
  const setting_order_coveredenable = getSettingValue("order_coveredenable");
  const setting_order_serviceenable = getSettingValue("order_serviceenable");
  // HEADER
  const setting_print_title = getSettingValue("print_title");
  const setting_print_header1 = getSettingValue("print_header1");
  const setting_print_header2 = getSettingValue("print_header2");
  const setting_print_header3 = getSettingValue("print_header3");
  const setting_print_header4 = getSettingValue("print_header4");
  const setting_print_header5 = getSettingValue("print_header5");
  const setting_print_directly_bill_fiscal = getSettingValue(
    "print_directly_bill_fiscal",
  );
  const setting_print_messageline1 = getSettingValue("print_messageline1");
  const setting_print_messageline2 = getSettingValue("print_messageline2");
  const setting_print_date = getSettingValue("print_date");
  const setting_print_directly_bill = getSettingValue("print_directly_bill");

  logPrintSettings("bill", {
    print_title: setting_print_title,
    print_header1: setting_print_header1,
    print_header2: setting_print_header2,
    print_header3: setting_print_header3,
    print_header4: setting_print_header4,
    print_header5: setting_print_header5,
    print_messageline1: setting_print_messageline1,
    print_messageline2: setting_print_messageline2,
    print_date: setting_print_date,
    print_directly_bill: setting_print_directly_bill,
    print_directly_bill_fiscal: setting_print_directly_bill_fiscal,
  });
  const labelWidth = config.get("customer.configurations.printer.labelWidth");
  const valueWidth = config.get("customer.configurations.printer.valueWidth");
  const { header, output, footer, printableOutput } = buildBillTemplate({
    order,
    lineWidth,
    descWidth,
    priceWidth,
    totalWidth,
    labelWidth,
    valueWidth,
    settings: {
      order_coveredenable: setting_order_coveredenable,
      order_serviceenable: setting_order_serviceenable,
      print_header1: setting_print_header1,
      print_header2: setting_print_header2,
      print_header3: setting_print_header3,
      print_header4: setting_print_header4,
      print_header5: setting_print_header5,
      print_directly_bill_fiscal: setting_print_directly_bill_fiscal,
      print_messageline1: setting_print_messageline1,
      print_messageline2: setting_print_messageline2,
      print_date: setting_print_date,
    },
  });

  // -----------------------------
  // 🧩 DEBUG MODE
  // -----------------------------
  if (debugEnabled) {
    const finalHtml = `<pre class="printBillContent">${header} ${output} ${footer}</pre>`;
    logEvent("INFO", "Bill debug preview generated", {
      orderId: order.id,
      table: order.table?.name,
    });
    logPrintPreview(
      logPreview,
      "bill-print",
      printableOutput,
      order,
      "debug",
      setting_print_directly_bill || "configured",
    );
    return finalHtml;
  } else {
    if (!setting_print_directly_bill) {
      logEvent(
        "WARN",
        "Setting 'print_directly_bill' not found. Skipping bill print",
      );
      return;
    }

    // Prepare printer
    const printer = initPrinter(setting_print_directly_bill, "bill");
    let isConnected = await printer.isPrinterConnected();

    // -----------------------------
    // 🖨️ REAL PRINTER MODE
    // -----------------------------
    if (isConnected) {
      logEvent("INFO", "Printer connected", {
        printer: setting_print_directly_bill,
        type: "bill",
      });

      printer.alignCenter();

      // Optional logo
      const logo_enabled = config.get("customer.configurations.logo.enabled");
      const logo_url = config.get("customer.configurations.logo.url");
      if (logo_enabled && logo_url) {
        await printer.printImage(logo_url);
        printer.newLine();
      }

      printer.setTextDoubleHeight();
      printer.setTextDoubleWidth();
      if (setting_print_title) printer.println(setting_print_title);
      printer.newLine();
      printer.alignCenter();
      printer.setTextNormal();
      header.split("\n").forEach((line) => {
        printer.println(line);
      });
      printer.alignLeft();

      // Print bill lines from output
      output.split("\n").forEach((line) => {
        printer.println(line);
      });
      printer.alignCenter();
      footer.split("\n").forEach((line) => {
        printer.println(line);
      });

      // QR code
      const qrcode_enabled = config.get(
        "customer.configurations.qrcode.enabled",
      );
      const qrcode_url = config.get("customer.configurations.qrcode.url");
      const qrcode_cellSize = config.get(
        "customer.configurations.qrcode.cellSize",
      );
      const qrcode_correction = config.get(
        "customer.configurations.qrcode.correction",
      );
      const qrcode_model = config.get("customer.configurations.qrcode.model");

      if (qrcode_enabled && qrcode_url) {
        printer.printQR(qrcode_url, {
          cellSize: qrcode_cellSize,
          correction: qrcode_correction,
          model: qrcode_model,
        });
      }

      executePrinter(printer);
    } else {
      logEvent("ERROR", "Printer network connection failed", {
        printer: setting_print_directly_bill,
        type: "bill",
      });
      logPrintPreview(
        logPreview,
        "bill-print",
        printableOutput,
        order,
        "printer-offline",
        setting_print_directly_bill,
      );
      ws.send("connectionError");
    }
  }
}
async function printTemplateFollowed(order, ws, debug) {
  const { output, printableOutput } = buildFollowedTemplate(order);
  const debugEnabled = isDebugPreviewEnabled(debug);

  if (debugEnabled) {
    // Final HTML for preview/debug
    const finalHtml = `<pre class="printComandaContent">${printableOutput}</pre>`;
    logEvent("INFO", "Followed debug preview generated", {
      orderId: order.id,
      table: order.table?.name,
    });
    logPrintPreview(
      logPreview,
      "followed-print",
      printableOutput,
      order,
      "debug",
      "configured",
    );
    return finalHtml;
  } else {
    const print_directly_following = getSettingValue(
      "print_directly_following",
    );
    if (!print_directly_following) {
      logEvent(
        "WARN",
        "Setting 'print_directly_following' not found. Skipping followed print",
      );
      return;
    }
    // Split by semicolon, trim spaces
    const printerIPs = print_directly_following
      .split(";")
      .map((ip) => ip.trim());

    // Print to all printers
    for (const ip of printerIPs) {
      await printFollowedToSinglePrinter(ip, order, ws, output);
    }
    return;
  }
}
async function printFollowedToSinglePrinter(ip, order, ws, output) {
  let printer = initPrinter(ip, "following");
  let isConnected = await printer.isPrinterConnected();

  if (!isConnected) {
    const msg = `Printer ${ip} connection failed`;
    logEvent("ERROR", msg, { printer: ip, type: "following" });
    logPrintPreview(
      logPreview,
      "followed-print",
      output,
      order,
      "printer-offline",
      ip,
    );
    ws.send(JSON.stringify({ message: msg, printer: ip }));
    return;
  }

  logEvent("INFO", "Printer connected", { printer: ip, type: "following" });

  // ---- PRINT START ----
  printer.alignLeft();
  printer.println("=== COMANDA ===");
  printer.newLine();

  printer.println(`TAVOLO: ${order.table?.name || ""}`);
  printer.println(`OSPITI: ${order.guests}`);
  printer.newLine();
  printer.println(`CAMERIERE: ${order.waiter?.name || ""}`);
  printer.println(`DATA: ${formatDateTime(order.order_date)}`);
  printer.newLine();

  printer.setTextDoubleHeight();
  printer.setTextDoubleWidth();
  printer.println("====== SEGUITO ======\n\n");

  executePrinter(printer);
}

/**
 * Init and return printer
 * @param {*} printer_IP_or_NAME
 * @returns
 */
function initPrinter(printer_IP_or_NAME, type) {
  const printer_width = config.get("customer.configurations.printer.lineWidth");
  const print_directly_comande_type = getSettingValue(
    "print_directly_comande_type",
  );
  const print_directly_bill_type = getSettingValue("print_directly_bill_type");

  let options = null;
  if (type == "comanda" || type == "following") {
    if (print_directly_comande_type == "IP") {
      options = {
        width: printer_width,
        type: PrinterTypes.EPSON, // Printer type: 'star' or 'epson'
        interface: "tcp://" + printer_IP_or_NAME + ":9100", // Printer interface
        characterSet: "SLOVENIA", // Printer character set - default: SLOVENIA
        removeSpecialCharacters: false, // Removes special characters - default: false
        lineCharacter: "=", // Set character for lines - default: "-"
        options: {
          // Additional options
          timeout: 5000, // Connection timeout (ms) [applicable only for network printers] - default: 3000
        },
      };
    } else if (print_directly_comande_type == "USB") {
      options = {
        width: printer_width,
        type: PrinterTypes.EPSON,
        interface: "printer:" + printer_IP_or_NAME,
        driver: require(
          electron ? "electron-printer" : "@thiagoelg/node-printer",
        ),
      };
    }
  } else if (type == "bill") {
    if (print_directly_bill_type == "IP") {
      options = {
        width: printer_width,
        type: PrinterTypes.EPSON, // Printer type: 'star' or 'epson'
        interface: "tcp://" + printer_IP_or_NAME + ":9100", // Printer interface
        characterSet: "SLOVENIA", // Printer character set - default: SLOVENIA
        removeSpecialCharacters: false, // Removes special characters - default: false
        lineCharacter: "=", // Set character for lines - default: "-"
        options: {
          // Additional options
          timeout: 5000, // Connection timeout (ms) [applicable only for network printers] - default: 3000
        },
      };
    } else if (print_directly_bill_type == "USB") {
      options = {
        width: printer_width,
        type: PrinterTypes.EPSON,
        interface: "printer:" + printer_IP_or_NAME,
        driver: require(
          electron ? "electron-printer" : "@thiagoelg/node-printer",
        ),
      };
    }
  } else {
    logEvent("ERROR", "Invalid printer type requested", { type });
  }
  const printer = new ThermalPrinter(options);
  return printer;
}
/**
 * Execute POS printer
 * @param {*} printer
 */
function executePrinter(printer) {
  printer.partialCut();
  try {
    let execute = printer.execute();
    logEvent("INFO", "Print job sent to printer");
  } catch (error) {
    logEvent("ERROR", "Print failed", { error: error.message || error });
  }
  printer.clear();
}

function getInitConfigurationSummary() {
  return {
    ecservice: getSettingValue("print_directly_ecservice_ip") || "-",
    followed: getSettingValue("print_directly_following") || "-",
    comande: getSettingValue("print_directly_comande") || "-",
    bill: getSettingValue("print_directly_bill") || "-",
  };
}

function logInitConfigurations(settingsCount) {
  const summary = getInitConfigurationSummary();
  const signature = JSON.stringify(summary);

  if (signature === lastInitConfigSignature) {
    logEvent(
      "INFO",
      "Action init-configurations received with unchanged values",
      {
        settings: settingsCount,
        ecservice: summary.ecservice,
      },
    );
    logCommand("init-configurations", {
      settings: settingsCount,
      status: "unchanged",
      ecservice: summary.ecservice,
    });
    return;
  }

  lastInitConfigSignature = signature;

  logEvent("INFO", "Action init-configurations applied", {
    settings: settingsCount,
    ecservice: summary.ecservice,
    followed: summary.followed,
    comande: summary.comande,
    bill: summary.bill,
  });
  logCommand("init-configurations", {
    settings: settingsCount,
    status: "applied",
    ecservice: summary.ecservice,
    followed: summary.followed,
    comande: summary.comande,
    bill: summary.bill,
  });
}