package com.ffii.core.utils

import org.apache.pdfbox.pdmodel.PDDocument
import org.slf4j.LoggerFactory
import java.io.File
import java.io.OutputStream
import java.net.Socket

/**
 * Utility class for generating and sending print jobs to a Canon printer using PDF.
 * This class requires the 'org.apache.pdfbox:pdfbox' dependency to be included in your project.
 */
open class CanonPrinterUtil {

    // Enum to represent valid duplex print modes
    enum class DuplexMode {
        SIMPLEX,           // Single-sided printing
        DUPLEX_LONG_EDGE,  // Double-sided, flip on long edge (portrait binding)
        DUPLEX_SHORT_EDGE  // Double-sided, flip on short edge (landscape binding)
    }

    companion object {
        private val log = LoggerFactory.getLogger(CanonPrinterUtil::class.java)
        /**
         * Detects if a PDF document is in landscape (horizontal) orientation.
         *
         * @param pdfFile The PDF file to check.
         * @return true if the document is landscape, false if portrait.
         */
        fun isLandscape(pdfFile: File): Boolean {
            if (!pdfFile.exists() || !pdfFile.canRead()) {
                return false
            }

            try {
                PDDocument.load(pdfFile).use { document ->
                    if (document.numberOfPages > 0) {
                        val page = document.getPage(0)
                        val mediaBox = page.mediaBox
                        val width = mediaBox.width
                        val height = mediaBox.height

                        // Landscape if width > height
                        return width > height
                    }
                }
            } catch (e: Exception) {
                println("DEBUG: Error detecting PDF orientation: ${e.message}")
            }
            return false
        }

        /**
         * Sends a PDF document directly to the specified Canon printer with duplex printing support.
         *
         * @param pdfFile The PDF file to be printed.
         * @param printerIp The IP address of the Canon printer.
         * @param printerPort The port of the Canon printer, typically 9100.
         * @param printQty The qty of print, default 1
         * @param duplexMode Valid values: SIMPLEX (single-sided), DUPLEX_LONG_EDGE (portrait), DUPLEX_SHORT_EDGE (landscape)
         * @throws Exception if there is an error during file processing or printing.
         */
        fun printPdfToCanon(pdfFile: File, printerIp: String, printerPort: Int, printQty: Int? = 1, duplexMode: DuplexMode = DuplexMode.DUPLEX_LONG_EDGE) {

            // Check if the file exists and is readable
            if (!pdfFile.exists() || !pdfFile.canRead()) {
                throw IllegalArgumentException("Error: File not found or not readable at path: ${pdfFile.absolutePath}")
            }

            try {
                // 1. Build PJL (Printer Job Language) commands for Canon printers
                val pjlCommands = StringBuilder()

                // PJL job start
                pjlCommands.append("\u001B%-12345X@PJL\n")
                pjlCommands.append("@PJL JOB NAME=\"PDF Print\"\n")

                // 2. Set duplex mode
                when (duplexMode) {
                    DuplexMode.SIMPLEX -> {
                        pjlCommands.append("@PJL SET DUPLEX=OFF\n")
                    }
                    DuplexMode.DUPLEX_LONG_EDGE -> {
                        pjlCommands.append("@PJL SET DUPLEX=ON\n")
                        pjlCommands.append("@PJL SET BINDING=LONGEDGE\n")
                    }
                    DuplexMode.DUPLEX_SHORT_EDGE -> {
                        pjlCommands.append("@PJL SET DUPLEX=ON\n")
                        pjlCommands.append("@PJL SET BINDING=SHORTEDGE\n")
                    }
                }

                // 3. Set number of copies
                if ((printQty ?: 1) > 1) {
                    pjlCommands.append("@PJL SET COPIES=${printQty ?: 1}\n")
                }

                // 4. Set paper size to A4
                pjlCommands.append("@PJL SET PAPER=A4\n")

                // 5. Enter PDF language mode
                pjlCommands.append("@PJL ENTER LANGUAGE=PDF\n")

                // PJL job end marker (before PDF content)
                pjlCommands.append("\u001B%-12345X\n")

                // 6. Read PDF file content
                val pdfContent = pdfFile.readBytes()

                // Print each copy for the specified quantity
                val copies = printQty ?: 1
                repeat(copies) { copyIndex ->
                    val tSend = System.nanoTime()
                    Socket(printerIp, printerPort).use { socket ->
                        val os: OutputStream = socket.getOutputStream()

                        // Send PJL commands
                        val printData = pjlCommands.toString().toByteArray(Charsets.US_ASCII)
                        os.write(printData)

                        // Send PDF content
                        os.write(pdfContent)

                        // Send job end marker
                        os.write("\u001B%-12345X".toByteArray(Charsets.US_ASCII))

                        os.flush()
                    }
                    val sendMs = (System.nanoTime() - tSend) / 1_000_000.0
                    log.info(
                        "Canon printPdfToCanon: copy {} of {} sendMs={} printer={}:{} pdfBytes={} (high sendMs: check LAN/WiFi, firewall, or printer 9100 queue)",
                        copyIndex + 1,
                        copies,
                        sendMs,
                        printerIp,
                        printerPort,
                        pdfContent.size,
                    )
                }
            } catch (e: Exception) {
                // Re-throw the exception with a more descriptive message
                throw Exception("Error processing print job for PDF: ${e.message}", e)
            }
        }
    }
}