package com.ffii.fpsms.modules.jobOrder.service

import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository
import com.ffii.fpsms.modules.jobOrder.web.model.PrintRequest
import com.ffii.fpsms.modules.jobOrder.web.model.LaserRequest
import org.springframework.stereotype.Service
import java.awt.Color
import java.awt.Font
import java.awt.Graphics2D
import java.awt.RenderingHints
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.imageio.ImageIO
import com.google.zxing.BarcodeFormat
import com.google.zxing.qrcode.QRCodeWriter
import java.net.Socket
import java.net.InetSocketAddress
import java.io.PrintWriter
import java.io.DataOutputStream
import java.nio.charset.Charset

import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.ConnectException
import java.net.SocketTimeoutException

// Data class to store bitmap bytes + width (for XML)
data class BitmapResult(val bytes: ByteArray, val width: Int)

@Service
open class PlasticBagPrinterService(
    val jobOrderRepository: JobOrderRepository,
) {

    fun generatePrintJobBundle(
        itemCode: String,
        lotNo: String,
        expiryDate: String,
        productName: String
    ): ByteArray {
        val baos = ByteArrayOutputStream()
        ZipOutputStream(baos).use { zos ->
            // Reduced heights by ~80% to hit the 12KB-17KB file size target
            // These heights (100-180) will produce much smaller BMP files
            val productBmp = createMonochromeBitmap(productName, 300)
            val codeBmp = createMonochromeBitmap(itemCode, 200)
            val expBmp = createMonochromeBitmap(expiryDate, 180)
            val qrBmp = createQrCodeBitmap("$itemCode|$lotNo|$expiryDate", 600)
            val lotBmp = createMonochromeBitmap(lotNo, 180)

            // All file names now prefixed with "TT_$itemCode"
            val prefix = "TT_$itemCode"
            val nameFile = "${prefix}_product.bmp"
            val codeFile = "${prefix}_code.bmp"
            val expFile = "${prefix}_expiry.bmp"
            val qrFile  = "${prefix}_qr.bmp"
            val lotFile = "${prefix}_lot.bmp"

            addToZip(zos, nameFile, productBmp.bytes)
            addToZip(zos, codeFile, codeBmp.bytes)
            addToZip(zos, lotFile, lotBmp.bytes)
            addToZip(zos, qrFile,  qrBmp.bytes)

            val imageXml = """
                <Legend type="Image.V1" PaperColor="White" BackgroundColor="DarkGray">
                    <DeviceInfo DeviceURI="NGEDriver[SDX40c.cadb94e3-677c-4bcb-9c80-88bf9526d6fe]/">
                        <DeviceName />
                    </DeviceInfo>
                    <Description />
                    <Mirrored>false</Mirrored>
                    <Orientation>DEG_0</Orientation>
                    <Width>5300</Width>
                    <Height>7100</Height>
                    <FieldList>
                        <Logo type="Logo.V1">
                            <Name>LOGO</Name>
                            <ID>4</ID>
                            <Geometry>
                                <X>250</X>
                                <Y>500</Y>
                                <Rotation>0</Rotation>
                            </Geometry>
                            <FieldColor>BLACK</FieldColor>
                            <FileName>$nameFile</FileName>
                            <Width>4914</Width>
                            <Height>845</Height>
                        </Logo>
                        <Logo type="Logo.V1">
                            <Name>LOGO_2</Name>
                            <ID>5</ID>
                            <Geometry>
                                <X>1000</X>
                                <Y>1250</Y>
                                <Rotation>0</Rotation>
                            </Geometry>
                            <FieldColor>BLACK</FieldColor>
                            <FileName>$codeFile</FileName>
                            <Width>3205</Width>
                            <Height>1173</Height>
                        </Logo>
                        <Logo type="Logo.V1">
                            <Name>LOGO_3</Name>
                            <ID>6</ID>
                            <Geometry>
                                <X>500</X>
                                <Y>2500</Y>
                                <Rotation>0</Rotation>
                            </Geometry>
                            <FieldColor>BLACK</FieldColor>
                            <FileName>$expFile</FileName>
                            <Width>4203</Width>
                            <Height>1173</Height>
                        </Logo>
                        <Logo type="Logo.V1">
                            <Name>LOGO_4</Name>
                            <ID>7</ID>
                            <Geometry>
                                <X>500</X>
                                <Y>3750</Y>
                                <Rotation>0</Rotation>
                            </Geometry>
                            <FieldColor>BLACK</FieldColor>
                            <FileName>$qrFile</FileName>
                            <Width>4353</Width>
                            <Height>3000</Height>
                        </Logo>
                    </FieldList>
                </Legend>""".trimIndent()

            // Image and Job files now also use TT_$itemCode
            addToZip(zos, "$prefix.image", imageXml.toByteArray(Charsets.UTF_8))

            val jobXml = """
                <Job>
                    <ImageFileName>$prefix.image</ImageFileName>
                </Job>""".trimIndent()

            addToZip(zos, "$prefix.job", jobXml.toByteArray(Charsets.UTF_8))
        }
        return baos.toByteArray()
    }

    private fun createMonochromeBitmap(text: String, targetHeight: Int): BitmapResult {
        // Step 1: Measure text width with temporary image
        val tempImg = BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY)
        val g2dTemp = tempImg.createGraphics()
        val font = Font("SimSun", Font.BOLD, (targetHeight * 0.92).toInt())
        g2dTemp.font = font
        val metrics = g2dTemp.getFontMetrics(font)
        val textWidth = metrics.stringWidth(text).coerceAtLeast(1)
        g2dTemp.dispose()

        // Step 2: Create actual image 
        // CHANGE 1: Changed 'val' to 'var' so we can reassign it on line 84
        var img = BufferedImage(textWidth, targetHeight, BufferedImage.TYPE_BYTE_BINARY)
        
        // CHANGE 2: Removed 'val g2d =' because it was unused
        img.createGraphics().apply {
            color = Color.WHITE
            fillRect(0, 0, textWidth, targetHeight)
            color = Color.BLACK
            this.font = font // use 'this.font' to be explicit inside apply
            setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF)
            drawString(text, 0, metrics.ascent)
            dispose()
        }

        // Step 3: Crop excess white space
        // This line (84) will now work because 'img' is a 'var'
        img = cropToContent(img)

        val baos = ByteArrayOutputStream()
        ImageIO.write(img, "bmp", baos)
        return BitmapResult(baos.toByteArray(), img.width)
    }
    
    fun testSmartDateX40Tcp(printerIp: String, port: Int = 3001, timeoutMs: Int = 3000): Pair<Boolean, String> {
        return try {
            Socket().use { socket ->
                socket.connect(InetSocketAddress(printerIp, port), timeoutMs)
                socket.soTimeout = 2000  // read timeout

                val output = PrintWriter(socket.getOutputStream(), true)
                output.println("~VR")  // or any simple command your printer understands
                output.flush()

                val input = BufferedReader(InputStreamReader(socket.getInputStream()))
                val response = try {
                    input.readLine() ?: "No response line"
                } catch (e: java.net.SocketTimeoutException) {
                    "No response within timeout"
                }

                if (response.isNotBlank() && response != "No response within timeout") {
                    true to "Connected successfully. Response: $response"
                } else {
                    true to "Connected (port open), but no meaningful response"
                }
            }
        } catch (e: SocketTimeoutException) {
            false to "Connection timed out (port may be closed or printer unreachable)"
        } catch (e: ConnectException) {
            false to "Connection refused (port closed or wrong IP)"
        } catch (e: Exception) {
            false to "Test failed: ${e.javaClass.simpleName} - ${e.message}"
        }
    }

    private fun createQrCodeBitmap(content: String, contentSize: Int, totalSize: Int = contentSize + 80): BitmapResult {
        if (totalSize < contentSize) throw IllegalArgumentException("totalSize must be >= contentSize")

        val writer = QRCodeWriter()
        val bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, contentSize, contentSize)

        val image = BufferedImage(totalSize, totalSize, BufferedImage.TYPE_BYTE_BINARY)
        val g = image.createGraphics()
        g.color = Color.WHITE
        g.fillRect(0, 0, totalSize, totalSize)

        val offset = (totalSize - contentSize) / 2
        for (x in 0 until contentSize) {
            for (y in 0 until contentSize) {
                if (bitMatrix.get(x, y)) {
                    image.setRGB(x + offset, y + offset, Color.BLACK.rgb)
                }
            }
        }
        g.dispose()

        val baos = ByteArrayOutputStream()
        ImageIO.write(image, "bmp", baos)

        return BitmapResult(baos.toByteArray(), image.width)
    }

    private fun cropToContent(img: BufferedImage): BufferedImage {
        val width = img.width
        val height = img.height
        var minX = width
        var minY = height
        var maxX = 0
        var maxY = 0

        for (x in 0 until width) {
            for (y in 0 until height) {
                if (img.getRGB(x, y) == Color.BLACK.rgb) {
                    minX = minOf(minX, x)
                    minY = minOf(minY, y)
                    maxX = maxOf(maxX, x)
                    maxY = maxOf(maxY, y)
                }
            }
        }

        if (minX > maxX || minY > maxY) return img  // empty image fallback

        val padding = 4
        val cropX = (minX - padding).coerceAtLeast(0)
        val cropY = (minY - padding).coerceAtLeast(0)
        val cropW = (maxX - minX + padding * 2).coerceAtMost(width - cropX)
        val cropH = (maxY - minY + padding * 2).coerceAtMost(height - cropY)

        return img.getSubimage(cropX, cropY, cropW, cropH)
    }

    private fun addToZip(zos: ZipOutputStream, entryName: String, content: ByteArray) {
        val entry = ZipEntry(entryName)
        zos.putNextEntry(entry)
        zos.write(content)
        zos.closeEntry()
    }

    // ────────────────────────────────────────────────
    // The rest of your printer communication methods remain unchanged
    // ────────────────────────────────────────────────

    fun sendDataFlexJob(request: PrintRequest) {
        Socket().use { socket ->
            try {
                socket.connect(InetSocketAddress(request.printerIp, request.printerPort), 5000)
                val output = socket.getOutputStream()
                val writer = PrintWriter(output, true)

                val command = buildString {
                    append("\u0002")  // <STX>
                    append("<ESC>JI;YOUR_JOB_NAME\r\n")
                    append("<ESC>VB;ItemCode;${request.itemCode}\r\n")
                    append("<ESC>VB;ItemName;${request.itemName}\r\n")
                    append("<ESC>VB;LotNo;${request.lotNo}\r\n")
                    append("<ESC>VB;ExpiryDate;${request.expiryDate}\r\n")
                    append("<ESC>VB;QRContent;lot=${request.lotNo}&item=${request.itemCode}\r\n")
                    append("<ESC>P;0\r\n")
                    append("\u0003")  // <ETX>
                }

                writer.print(command)
                writer.flush()
                println("DataFlex: Print job sent to ${request.printerIp}:${request.printerPort}")
            } catch (e: Exception) {
                throw RuntimeException("DataFlex communication failed: ${e.message}", e)
            }
        }
    }

    fun sendLaserMark(request: LaserRequest) {
        Socket().use { socket ->
            socket.connect(InetSocketAddress(request.printerIp, request.printerPort.toInt()), 3000)
            val writer = PrintWriter(socket.getOutputStream(), true)
            val command = "STRSET|${request.templateId}|LOT=${request.lotNo}|EXP=${request.expiryDate}\n"
            writer.print(command)
            writer.flush()
        }
    }

    fun sendLaserPreview(request: LaserRequest) {
        Socket().use { socket ->
            socket.connect(InetSocketAddress(request.printerIp, request.printerPort), 3000)
            val writer = PrintWriter(socket.getOutputStream(), true)
            val command = """
                JOBLOAD|${request.templateId}
                REDLIGHT|1
            """.trimIndent()
            writer.println(command)
            writer.flush()
        }
    }

    fun sendTscPrintJob(request: PrintRequest) {
        Socket().use { socket ->
            try {
                socket.connect(InetSocketAddress(request.printerIp, request.printerPort), 3000)
                val out = DataOutputStream(socket.getOutputStream())

                val tspl = StringBuilder()
                    .append("SIZE 100 mm, 50 mm\n")
                    .append("GAP 3 mm, 0 mm\n")
                    .append("DIRECTION 1\n")
                    .append("CLS\n")
                    .append("TEXT 50,40,\"ROMAN.TTF\",0,1,1,\"ITEM: ${request.itemCode}\"\n")
                    .append("TEXT 50,80,\"ROMAN.TTF\",0,1,1,\"NAME: ${request.itemName}\"\n")
                    .append("TEXT 50,120,\"ROMAN.TTF\",0,1,1,\"EXP: ${request.expiryDate}\"\n")
                    .append("TEXT 50,160,\"ROMAN.TTF\",0,1,1,\"LOT: ${request.lotNo}\"\n")
                    .append("QRCODE 450,40,M,4,A,0,\"${request.lotNo}\"\n")
                    .append("TEXT 450,180,\"ROMAN.TTF\",0,1,1,\"SCAN LOT\"\n")
                    .append("PRINT 1,1\n")
                    .toString()

                val bytes = tspl.toByteArray(Charset.forName("TIS-620"))
                out.write(bytes)
                out.flush()
                println("TSC: Print job sent to ${request.printerIp}")
            } catch (e: Exception) {
                throw RuntimeException("TSC Printer Error: ${e.message}")
            }
        }
    }

    fun sendLaserTcpUpdateAndMark(
        ip: String = "192.168.76.10",
        port: Int = 45678,
        textChannel3: String,               // required – content for channel 3
        textChannel4: String,               // required – content for channel 4
        text3ObjectName: String = "Text3",  // EZCAD object name for channel 3
        text4ObjectName: String = "Text4"   // EZCAD object name for channel 4
    ): Boolean {
        var socket: Socket? = null
        var writer: PrintWriter? = null
        var reader: BufferedReader? = null

        try {
            println("Han's Laser TCP: 正在連接 $ip:$port ...")
            socket = Socket(ip, port).apply {
                soTimeout = 8000  // 8秒超時，避免卡死
            }

            writer = PrintWriter(socket.getOutputStream(), true)
            reader = BufferedReader(InputStreamReader(socket.getInputStream(), Charsets.UTF_8))

            // Helper function: send command and check response
            fun send(cmd: String): Boolean {
                writer.println("$cmd\r\n")
                writer.flush()
                Thread.sleep(300)  // give device time to process

                val resp = reader.readLine()?.trim() ?: ""
                println("命令: $cmd → 回應: $resp")
                return resp.startsWith("OK")
            }

            // Update channel 3 text
            val success3 = send("""E3_SetTextObject "$text3ObjectName" "$textChannel3"""")

            // Update channel 4 text
            val success4 = send("""E3_SetTextObject "$text4ObjectName" "$textChannel4"""")

            // Trigger marking
            val successMark = send("E3_StartMarking")

            val allSuccess = success3 && success4 && successMark

            if (allSuccess) {
                println("Han's Laser: 更新並打標成功")
            } else {
                println("Han's Laser: 部分命令失敗")
            }

            return allSuccess

        } catch (e: Exception) {
            println("Han's Laser TCP 錯誤: ${e.message}")
            e.printStackTrace()
            return false
        } finally {
            reader?.close()
            writer?.close()
            socket?.close()
            println("Han's Laser TCP 連接已關閉")
        }
    }

    fun sendDataFlex6330Zpl(request: PrintRequest) {
        Socket().use { socket ->
            try {
                // Connect with timeout
                socket.connect(InetSocketAddress(request.printerIp, request.printerPort), 5000)
                socket.soTimeout = 5000  // read timeout if expecting response (optional)

                val out = socket.getOutputStream()

                // Build ZPL dynamically
                val zpl = buildString {
                    append("^XA\n")
                    append("^PW500\n")           // Print width ~42mm @300dpi; adjust 400-630 based on head
                    append("^LL280\n")           // Label height ~23mm; tune for your pouch
                    append("^PON\n")             // Normal orientation
                    append("^CI28\n")            // UTF-8 / extended char set for Chinese

                    // Chinese product name / description (top)
                    append("^FO20,20^A@N,36,36,E:SIMSUN.FNT^FD${request.itemName}^FS\n")  // Assumes font loaded

                    // Item code
                    append("^FO20,80^A0N,32,32^FD${request.itemCode}^FS\n")

                    // Expiry date
                    append("^FO20,120^A0N,28,28^FDEXP: ${request.expiryDate}^FS\n")

                    // Lot / Batch No.
                    append("^FO20,160^A0N,28,28^FDLOT: ${request.lotNo}^FS\n")

                    // QR code encoding lotNo (or combine: item|lot|exp)
                    // Position right side, mag 6 (~good size), model 2
                    val qrData = request.lotNo  // or "${request.itemCode}|${request.lotNo}|${request.expiryDate}"
                    append("^FO320,20^BQN,2,6^FDQA,$qrData^FS\n")  // QA, prefix for alphanumeric mode

                    append("^XZ\n")
                }

                // Send as bytes (UTF-8 safe)
                out.write(zpl.toByteArray(Charsets.UTF_8))
                out.flush()

                println("DataFlex 6330 ZPL: Print job sent to ${request.printerIp}:${request.printerPort}")
                // Optional: read response if printer echoes anything (rare in raw ZPL)
                // val response = socket.getInputStream().readBytes().decodeToString()
                // println("Response: $response")

            } catch (e: Exception) {
                throw RuntimeException("DataFlex 6330 ZPL communication failed: ${e.message}", e)
            }
        }
    }
}