const { exec } = require("child_process");
const fs = require("fs");
const path = require("path");
const os = require("os");
const PrinterDetector = require("./printer-detector");
const { createLogger } = require("./lib/logger");
const { printRawFileToWindowsQueue } = require("./lib/spooler-raw");
const { receiptKindToCopyType } = require("./lib/receipt-kind");
const { loadTemplate } = require("./lib/receipt-template");
const { resolveTemplateForJob } = require("./lib/template-engine-v2");
const { renderReceipt } = require("./lib/receipt-renderer");
const { normalizePosPayload } = require("./lib/pos-payload");
const { getLogsDir } = require("./lib/paths");

const logger = createLogger();

function resolvePrinterMode() {
  const raw = (process.env.PRINTER_MODE || "").trim().toLowerCase();
  if (raw === "spooler" || raw === "tcp" || raw === "raw" || raw === "auto") {
    return raw;
  }
  return process.platform === "win32" ? "spooler" : "auto";
}

// Printer manager class
class PrinterManager {
  constructor() {
    this.detector = new PrinterDetector();
    this.currentPrinter = null;
    this.printMethods = [];
    this.retryCount = 0;
    this.maxRetries = 3;
    this.printerMode = resolvePrinterMode();

    // Business config from .env
    this.config = {
      BUSINESS_NAME: process.env.BUSINESS_NAME || "SIRAYA",
      BUSINESS_PHONE: process.env.BUSINESS_PHONE || "0325-4410555",
      BUSINESS_WEBSITE: process.env.BUSINESS_WEBSITE || "pos.siraya.food",
      BUSINESS_TAGLINE:
        process.env.BUSINESS_TAGLINE || "Broast . Burgers . Fries",
    };

    // Initialize printer
    this.initializePrinter();
  }

  async initializePrinter() {
    logger.info("🔄 Initializing printer manager...");

    // Try to load saved config first
    const hasConfig = this.detector.loadConfig();
    await this.detector.detectAllPrinters();

    if (!hasConfig || !this.detector.selectedPrinter) {
      this.detector.autoSelectPrinter();
    } else {
      const savedName = this.detector.selectedPrinter?.name;
      const stillThere = this.detector.detectedPrinters.find(
        (p) => p.name === savedName,
      );
      if (stillThere) {
        this.detector.selectedPrinter = stillThere;
      } else if (this.detector.detectedPrinters.length > 0) {
        this.detector.autoSelectPrinter();
      } else {
        this.detector.selectedPrinter = null;
      }
      this.detector.saveConfig();
    }

    this.currentPrinter = this.detector.selectedPrinter;

    if (!this.currentPrinter) {
      logger.warn("⚠️ No printer selected — configure one in Siraya Printer app");
      this.printMethods = [];
      return;
    }

    // Set permissions if needed
    await this.detector.setPrinterPermissions();

    // Setup print methods based on printer type
    this.setupPrintMethods();

    logger.info(`✅ Printer initialized: ${this.currentPrinter.name}`);
    logger.info(
      `📁 Type: ${this.currentPrinter.type}, Device: ${this.currentPrinter.devicePath}`,
    );

    // Auto-reconnect if printer disconnects
    setInterval(() => this.checkPrinterConnection(), 30000);
  }

  setupPrintMethods() {
    this.printMethods = [];

    if (this.printerMode === "spooler" && process.platform === "win32") {
      this.setupSpoolerPrintMethods();
      return;
    }

    if (this.printerMode === "tcp") {
      this.setupTcpPrintMethods();
      return;
    }

    switch (this.currentPrinter.type) {
      case "Spooler":
        this.setupSpoolerPrintMethods();
        break;
      case "USB":
        this.setupUSBPrintMethods();
        break;
      case "Network":
        this.setupNetworkPrintMethods();
        break;
      case "Network/CUPS":
      case "CUPS":
        this.setupCUPSPrintMethods();
        break;
      case "Windows":
        this.setupWindowsPrintMethods();
        break;
      case "Serial":
        this.setupSerialPrintMethods();
        break;
      default:
        this.setupUniversalPrintMethods();
    }
  }

  setupSpoolerPrintMethods() {
    const name =
      (process.env.PRINTER_SPOOLER_NAME || "").trim() ||
      this.currentPrinter?.spoolerName ||
      this.currentPrinter?.name ||
      this.currentPrinter?.devicePath;

    if (!name) {
      throw new Error("No Windows spooler queue configured (PRINTER_SPOOLER_NAME)");
    }

    this.printMethods = [
      (file) => printRawFileToWindowsQueue(name, file),
      (file) => this.executeCommand(`print /d:"${name}" "${file}"`),
    ];
  }

  setupTcpPrintMethods() {
    const host =
      process.env.PRINTER_NETWORK_ADDRESS ||
      this.currentPrinter?.networkHost ||
      "192.168.1.100";
    const port = parseInt(
      process.env.PRINTER_NETWORK_PORT ||
        this.currentPrinter?.networkPort ||
        "9100",
      10,
    );
    const device = `tcp://${host}:${port}`;
    this.currentPrinter = {
      type: "Network",
      name: `TCP ${host}:${port}`,
      devicePath: device,
      autoDetected: false,
      platform: process.platform,
      driver: "Raw TCP",
    };
    this.setupNetworkPrintMethods();
  }

  setupUSBPrintMethods() {
    const device = this.currentPrinter.devicePath;
    const methods = [];

    if (process.platform === "linux") {
      const {
        resolveLinuxCupsQueueName,
        printRawViaCups,
        clearCupsQueueIfStuck,
        writeRawToDevice,
      } = require("./lib/linux-cups");
      const cups = resolveLinuxCupsQueueName(this.currentPrinter);
      if (cups) {
        methods.push(async (file) => {
          clearCupsQueueIfStuck(cups);
          printRawViaCups(cups, file);
        });
      }
      if (device && fs.existsSync(device)) {
        methods.push((file) => writeRawToDevice(device, file));
      }
    } else if (process.platform === "win32") {
      methods.push(
        (file) =>
          this.executeCommand(
            `type "${file}" > "\\\\.\\${device.replace("USB\\\\", "")}"`,
          ),
        (file) => this.executeCommand(`copy "${file}" "${device}"`),
      );
    }

    this.printMethods = methods.length ? methods : [
      (file) => this.executeCommand(`cat "${file}" > "${device}"`),
    ];
  }

  setupNetworkPrintMethods() {
    const device = this.currentPrinter.devicePath;

    this.printMethods = [
      // Method 1: Netcat (Linux) - FIXED
      (file) => {
        const [host, port] = device.replace("tcp://", "").split(":");
        return this.executeCommand(
          `cat "${file}" | nc ${host} ${port || 9100}`,
        );
      },

      // Method 2: PowerShell (Windows) - FIXED
      (file) => {
        const [host, port] = device.replace("tcp://", "").split(":");
        return this.executeCommand(
          `powershell -Command "$fileContent = Get-Content '${file}' -Raw; $tcpClient = New-Object System.Net.Sockets.TcpClient('${host}', ${port || 9100}); $stream = $tcpClient.GetStream(); $writer = New-Object System.IO.StreamWriter($stream); $writer.Write($fileContent); $writer.Flush(); $tcpClient.Close();"`,
        );
      },

      // Method 3: curl (if printer supports) - FIXED
      (file) =>
        this.executeCommand(`curl -X POST --data-binary @"${file}" ${device}`),
    ];
  }

  setupCUPSPrintMethods() {
    const printerName = this.currentPrinter.name;
    const {
      printRawViaCups,
      clearCupsQueueIfStuck,
      writeRawToDevice,
      resolveLinuxDevicePath,
      getCupsQueueStatus,
    } = require("./lib/linux-cups");

    const methods = [];

    if (process.platform === "linux") {
      const device = resolveLinuxDevicePath(this.currentPrinter);
      if (device && fs.existsSync(device)) {
        try {
          fs.accessSync(device, fs.constants.W_OK);
          methods.push((file) => writeRawToDevice(device, file));
        } catch {
          /* device not writable for this service user */
        }
      }
    }

    methods.push(
      async (file) => {
        clearCupsQueueIfStuck(printerName);
        printRawViaCups(printerName, file);
        if (process.platform === "linux") {
          await new Promise((resolve) => setTimeout(resolve, 1200));
          const st = getCupsQueueStatus(printerName);
          if (st?.stuck) {
            throw new Error("CUPS queue stuck after submit");
          }
        }
      },
      (file) => this.executeCommand(`lpr -P "${printerName}" -o raw "${file}"`),
    );

    if (process.platform === "linux") {
      const device = resolveLinuxDevicePath(this.currentPrinter);
      if (device && fs.existsSync(device)) {
        methods.push((file) => writeRawToDevice(device, file));
      }
    }

    this.printMethods = methods;
  }

  setupWindowsPrintMethods() {
    const printerName = this.currentPrinter.name;

    this.printMethods = [
      // Method 1: Windows print command - FIXED
      (file) => this.executeCommand(`print /d:"${printerName}" "${file}"`),

      // Method 2: PowerShell printing - FIXED
      (file) =>
        this.executeCommand(
          `powershell -Command "Get-Content '${file}' -Raw | Out-Printer -Name '${printerName}'"`,
        ),
    ];
  }

  setupSerialPrintMethods() {
    const device = this.currentPrinter.devicePath;
    const baudRate = this.currentPrinter.baudRate || 9600;

    this.printMethods = [
      // Method 1: stty + cat (Linux) - FIXED
      (file) =>
        this.executeCommand(
          `stty -F "${device}" ${baudRate} && cat "${file}" > "${device}"`,
        ),

      // Method 2: Windows serial (PowerShell) - FIXED
      (file) =>
        this.executeCommand(
          `powershell -Command "$port = New-Object System.IO.Ports.SerialPort('${device}', ${baudRate}); $port.Open(); $content = Get-Content '${file}' -Raw; $port.Write($content); $port.Close();"`,
        ),
    ];
  }

  setupUniversalPrintMethods() {
    // Fallback methods that work on most systems
    this.printMethods = [
      // Try common USB paths - FIXED
      (file) => this.executeCommand(`cat "${file}" > /dev/usb/lp0`),
      (file) => this.executeCommand(`cat "${file}" > /dev/usb/lp1`),
      (file) => this.executeCommand(`cat "${file}" > /dev/lp0`),

      // Try CUPS default - FIXED
      (file) => this.executeCommand(`lp -o raw "${file}"`),

      // Try Windows default - FIXED
      (file) => this.executeCommand(`print "${file}"`),
    ];
  }

  async executeCommand(command) {
    return new Promise((resolve, reject) => {
      logger.debug(`Executing command: ${command.substring(0, 100)}...`);
      exec(command, { timeout: 10000 }, (error, stdout, stderr) => {
        if (error) {
          logger.error(`Command failed: ${stderr || error.message}`);
          reject(new Error(`Command failed: ${stderr || error.message}`));
        } else {
          logger.debug(
            `Command succeeded: ${stdout ? stdout.substring(0, 50) : "No output"}`,
          );
          resolve(stdout);
        }
      });
    });
  }

  async checkPrinterConnection() {
    try {
      if (!this.currentPrinter) {
        this.retryCount++;
        if (this.retryCount >= this.maxRetries) {
          logger.warn("No printer selected. Attempting re-detection...");
          await this.initializePrinter();
        }
        return false;
      }
      this.retryCount = 0;
      return true;
    } catch (error) {
      logger.error("Connection check failed:", error.message);
      return false;
    }
  }

  resolveTemplate(jobMeta = {}) {
    try {
      return resolveTemplateForJob({
        receiptKind: jobMeta.receiptKind || "customer_receipt",
        templateId: jobMeta.templateId,
      });
    } catch {
      return loadTemplate(this.config);
    }
  }

  async generateReceiptData(order, jobMeta = {}) {
    const { loadSettings } = require("./lib/settings-store");
    const settings = loadSettings();
    const base = this.resolveTemplate(jobMeta);
    const template = {
      ...base,
      businessSettings: settings.business || {},
    };
    const normalized = normalizePosPayload(order, this.config);
    return await renderReceipt(template, normalized, {
      receiptKind: jobMeta.receiptKind,
      businessConfig: this.config,
    });
  }

  // ✅ FIXED: Main print function
  async printReceipt(orderData, jobMeta = {}) {
    const normalized = normalizePosPayload(orderData, this.config);
    const copyType =
      normalized.copyType ||
      (jobMeta.receiptKind
        ? receiptKindToCopyType(jobMeta.receiptKind)
        : "COUNTER");
    const payload = { ...normalized, copyType };

    logger.info(
      `🖨️ Printing receipt for order: ${payload.orderNumber || "Unknown"} (${copyType})`,
    );

    const receiptData = await this.generateReceiptData(payload, jobMeta);

    // Create temp file with receipt data
    const tempFile = path.join(os.tmpdir(), `receipt-${Date.now()}.txt`);

    try {
      // Write receipt data as binary (important for ESC/POS commands)
      fs.writeFileSync(tempFile, receiptData, { encoding: "binary" });

      logger.debug(
        `📄 Created temp file: ${tempFile} (${receiptData.length} bytes)`,
      );

      let lastError = null;
      let success = false;

      // Try all print methods
      for (let i = 0; i < this.printMethods.length; i++) {
        try {
          const method = this.printMethods[i];
          logger.info(
            `🔄 Trying print method ${i + 1}/${this.printMethods.length}`,
          );

          await method(tempFile); // FIXED: Call method with file path

          logger.info(`✅ Printed using method ${i + 1}`);
          success = true;

          // Clean up
          fs.unlinkSync(tempFile);

          return {
            success: true,
            method: i + 1,
            printer: this.currentPrinter.name,
            orderNumber: orderData.orderNumber,
            timestamp: new Date().toISOString(),
          };
        } catch (error) {
          lastError = error;
          logger.warn(`Method ${i + 1} failed: ${error.message}`);
          // Continue to next method
        }
      }

      // All methods failed
      if (fs.existsSync(tempFile)) {
        fs.unlinkSync(tempFile);
      }

      const errorMsg = `All print methods failed. Last error: ${lastError?.message}`;
      logger.error(errorMsg);
      throw new Error(errorMsg);
    } catch (error) {
      // Clean up temp file if it exists
      if (fs.existsSync(tempFile)) {
        fs.unlinkSync(tempFile);
      }

      logger.error("Print failed:", error);
      throw error;
    }
  }

  // Get printer status
  getStatus() {
    const p = this.currentPrinter;
    const connected = Boolean(
      p &&
        !p.isFallback &&
        (p.autoDetected !== false ||
          Boolean((process.env.PRINTER_SPOOLER_NAME || "").trim())),
    );
    return {
      connected,
      printer: p,
      retryCount: this.retryCount,
      config: this.config,
      logsDir: getLogsDir(),
      printerMode: this.printerMode,
      timestamp: new Date().toISOString(),
      service: "Siraya Printer Service v3.0",
    };
  }

  // Rescan and select new printer
  async rescanPrinters() {
    logger.info("Rescanning for printers...");
    await this.detector.detectAllPrinters();

    if (this.detector.detectedPrinters.length > 0) {
      // Auto-select best printer
      this.detector.autoSelectPrinter();
      this.currentPrinter = this.detector.selectedPrinter;
      this.setupPrintMethods();
      return this.detector.detectedPrinters;
    }

    return [];
  }

  // Manually select printer
  selectPrinter(printerIndex) {
    if (
      printerIndex >= 0 &&
      printerIndex < this.detector.detectedPrinters.length
    ) {
      this.currentPrinter = this.detector.detectedPrinters[printerIndex];
      this.detector.selectedPrinter = this.currentPrinter;
      this.detector.saveConfig();
      this.setupPrintMethods();
      logger.info(`Selected printer: ${this.currentPrinter.name}`);
      return true;
    }
    return false;
  }
}

// Singleton instance
const printerManager = new PrinterManager();

module.exports = {
  printerManager,
  PrinterManager,
};
