package com.example.ops_android.printer

import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import com.example.ops_android.data.model.Item
import com.example.ops_android.data.model.PurchaseOrder
import com.example.ops_android.data.model.WorkOrder
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.oned.Code128Writer
import com.google.zxing.qrcode.QRCodeWriter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

object PrintHelper {

    private val printMutex = Mutex()
    private const val DOTS_PER_MM = 8

    private fun mmToDots(mm: Int): Int = mm * DOTS_PER_MM

    private fun buildQrContent(module: String, id: Int): String {
        val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
        return JSONObject().apply {
            put("v", "v1")
            put("module", module)
            put("id", id)
            put("printedAt", sdf.format(Date()))
            put("type", "ops_sys")
        }.toString()
    }

    private fun generateQRBitmap(content: String, size: Int): Bitmap {
        val hints = mapOf(EncodeHintType.MARGIN to 1)
        val bitMatrix = QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, size, size, hints)
        val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
        for (y in 0 until size) {
            for (x in 0 until size) {
                bitmap.setPixel(x, y, if (bitMatrix[x, y]) Color.BLACK else Color.WHITE)
            }
        }
        return bitmap
    }

    private fun drawTextBlock(
        canvas: Canvas,
        label: String,
        value: String,
        x: Float,
        y: Float,
        maxWidth: Float,
        maxLines: Int = 1,
        textSize: Float = 22f,
        bold: Boolean = false,
        lineSpacingExtra: Float = 4f
    ): Float {
        val text = "$label$value"
        val paint = TextPaint().apply {
            color = Color.BLACK
            this.textSize = textSize
            isAntiAlias = true
            isFakeBoldText = bold
        }

        val builder = StaticLayout.Builder.obtain(text, 0, text.length, paint, maxWidth.toInt())
            .setAlignment(Layout.Alignment.ALIGN_NORMAL)
            .setMaxLines(maxLines)
            .setEllipsize(android.text.TextUtils.TruncateAt.END)
            .setLineSpacing(lineSpacingExtra, 1f)

        val layout = builder.build()
        canvas.save()
        canvas.translate(x, y)
        layout.draw(canvas)
        canvas.restore()

        return y + layout.height
    }

    private fun buildWorkOrderLabelBitmap(wo: WorkOrder, widthDots: Int, heightDots: Int): Bitmap {
        val bitmap = Bitmap.createBitmap(widthDots, heightDots, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        canvas.drawColor(Color.WHITE)

        val padding = 12
        val dividerX = (widthDots * 0.48).toInt()
        val leftInnerWidth = dividerX - padding * 2
        val qrSize = (leftInnerWidth * 0.95).toInt().coerceAtLeast(140)
        val qrX = (dividerX - qrSize) / 2
        val qrY = padding

        val qrContent = buildQrContent("workorder", wo.id)
        val qrBitmap = generateQRBitmap(qrContent, qrSize)
        canvas.drawBitmap(qrBitmap, qrX.toFloat(), qrY.toFloat(), null)

        val sepPaint = Paint().apply {
            color = Color.BLACK
            strokeWidth = 1.5f
            isAntiAlias = true
        }
        canvas.drawLine(dividerX.toFloat(), padding.toFloat(), dividerX.toFloat(), (heightDots - padding).toFloat(), sepPaint)

        val textX = (dividerX + padding).toFloat()
        val textMaxWidth = (widthDots - dividerX - padding * 2).toFloat()
        var textY = (padding - 2).toFloat()
        val maxTextY = (heightDots - padding).toFloat()

        textY = drawTextBlock(canvas, "", "工单：${wo.id}", textX, textY, textMaxWidth, textSize = 32f, bold = true, lineSpacingExtra = 6f)

        if (textY < maxTextY) {
            textY = drawTextBlock(canvas, "", "标题：${wo.title ?: "-"}", textX, textY, textMaxWidth, maxLines = 3, textSize = 28f, lineSpacingExtra = 4f)
        }

        if (textY < maxTextY) {
            textY = drawTextBlock(canvas, "", "描述：${wo.description ?: "-"}", textX, textY, textMaxWidth, maxLines = 6, textSize = 28f, lineSpacingExtra = 4f)
        }

        val customerNames = wo.linkedCustomers
            ?.mapNotNull { c ->
                listOfNotNull(c.first_name, c.last_name)
                    .joinToString(" ")
                    .ifBlank { null }
            }
            ?.joinToString(", ")
            ?: "-"
        val customerText = "客户：$customerNames"
        val customerY = qrY + qrSize + 4
        val leftTextMaxWidth = (dividerX - padding * 2).toFloat()
        if (customerY < heightDots - padding) {
            drawTextBlock(canvas, "", customerText, padding.toFloat(), customerY.toFloat(), leftTextMaxWidth, maxLines = 2, textSize = 28f, lineSpacingExtra = 4f)
        }

        return bitmap
    }

    suspend fun printOrderLabel(
        printerManager: PrinterManager,
        order: PurchaseOrder,
        blackMarkEnabled: Boolean = true,
        unwindMm: Int = 40
    ): Result<Unit> = printMutex.withLock {
        withContext(Dispatchers.IO) {
            try {
                printerManager.initDevice().onFailure { return@withContext Result.failure(it) }
                printerManager.setFontSize(24)
                printerManager.setBold(true)
                printerManager.addText("采购单: ${order.title ?: "-"}\n")
                printerManager.setFontSize(20)
                printerManager.setBold(false)
                if (order.id > 0) {
                    printerManager.addBarcode("PO-${order.id}")
                }
                if (blackMarkEnabled) {
                    printerManager.addLineFeed(2)
                    printerManager.enableMarkDetection(true)
                } else {
                    printerManager.addLineFeed((unwindMm / 7).toInt().coerceAtLeast(2))
                }
                printerManager.commit()
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }

    suspend fun printWorkOrderLabel(
        printerManager: PrinterManager,
        workOrder: WorkOrder,
        labelWidthMm: Int = 53,
        labelHeightMm: Int = 40,
        blackMarkEnabled: Boolean = true,
        unwindMm: Int = 40,
        printRetractMm: Int = 10
    ): Result<Unit> = printMutex.withLock {
        withContext(Dispatchers.IO) {
            try {
                printerManager.initDevice().onFailure { return@withContext Result.failure(it) }
                if (printRetractMm > 0) {
                    printerManager.setUnwindLength(mmToDots(printRetractMm))
                }
                val widthDots = mmToDots(labelWidthMm)
                val heightDots = mmToDots(labelHeightMm)
                val bitmap = buildWorkOrderLabelBitmap(workOrder, widthDots, heightDots)
                printerManager.printImage(bitmap, 1)
                if (blackMarkEnabled) {
                    printerManager.addLineFeed(2)
                    printerManager.enableMarkDetection(true)
                } else {
                    printerManager.addLineFeed((unwindMm / 7).toInt().coerceAtLeast(2))
                }
                printerManager.commit()
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }

    private fun buildItemLabelBitmap(item: Item, widthDots: Int, heightDots: Int): Bitmap {
        val bitmap = Bitmap.createBitmap(widthDots, heightDots, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        canvas.drawColor(Color.WHITE)

        val padding = 12
        val barcodeReserve = if (!item.serial_number.isNullOrBlank()) 70 else 0
        val dividerX = (widthDots * 0.48).toInt()
        val leftInnerWidth = dividerX - padding * 2
        val qrSize = (leftInnerWidth * 0.95).toInt().coerceAtLeast(140)
        val qrX = (dividerX - qrSize) / 2
        val qrY = padding

        val qrContent = buildQrContent("item", item.id)
        val qrBitmap = generateQRBitmap(qrContent, qrSize)
        canvas.drawBitmap(qrBitmap, qrX.toFloat(), qrY.toFloat(), null)

        val sepPaint = Paint().apply {
            color = Color.BLACK
            strokeWidth = 1.5f
            isAntiAlias = true
        }

        val textX = (dividerX + padding).toFloat()
        val textMaxWidth = (widthDots - dividerX - padding * 2).toFloat()
        var textY = (padding - 2).toFloat()
        val maxTextY = heightDots - padding - barcodeReserve

        textY = drawTextBlock(canvas, "", "物品：${item.id}", textX, textY, textMaxWidth, textSize = 32f, bold = true, lineSpacingExtra = 6f)

        if (textY < maxTextY) {
            textY = drawTextBlock(canvas, "", "名称：${item.name ?: "-"}", textX, textY, textMaxWidth, maxLines = 3, textSize = 25f, lineSpacingExtra = 4f)
        }

        if (textY < maxTextY && item.quantity != 1) {
            textY = drawTextBlock(canvas, "", "数量：${item.quantity}", textX, textY, textMaxWidth, textSize = 25f, lineSpacingExtra = 4f)
        }

        if (textY < maxTextY && !item.containerBreadcrumb.isNullOrBlank()) {
            textY = drawTextBlock(canvas, "", "位置：${item.containerBreadcrumb}", textX, textY, textMaxWidth, maxLines = 4, textSize = 25f, lineSpacingExtra = 4f)
        }

        canvas.drawLine(dividerX.toFloat(), padding.toFloat(), dividerX.toFloat(), maxTextY.toFloat(), sepPaint)

        if (!item.linkedCustomers.isNullOrEmpty()) {
            val customerNames = item.linkedCustomers
                .mapNotNull { c ->
                    listOfNotNull(c.first_name, c.last_name)
                        .joinToString(" ")
                        .ifBlank { null }
                }
                .joinToString(", ")
            val customerText = "客户：$customerNames"
            val customerY = qrY + qrSize + 4
            val leftTextMaxWidth = (dividerX - padding * 2).toFloat()
            if (customerY < maxTextY) {
                drawTextBlock(canvas, "", customerText, padding.toFloat(), customerY.toFloat(), leftTextMaxWidth, maxLines = 2, textSize = 20f, lineSpacingExtra = 2f)
            }
        }

        if (!item.serial_number.isNullOrBlank()) {
            canvas.drawLine(padding.toFloat(), maxTextY.toFloat(), (widthDots - padding).toFloat(), maxTextY.toFloat(), sepPaint)

            val snTextY = maxTextY + 4
            val snTextHeight = drawTextBlock(canvas, "", item.serial_number ?: "",
                padding.toFloat(), snTextY.toFloat(),
                (widthDots - padding * 2).toFloat(),
                maxLines = 1, textSize = 22f, lineSpacingExtra = 0f
            ) - snTextY

            val barcodeWidth = widthDots - padding * 4
            val barcodeHeight = 35
            val barcodeX = (widthDots - barcodeWidth) / 2
            val barcodeY = snTextY + snTextHeight + 4

            val code128 = Code128Writer()
            val barcodeMatrix = code128.encode(item.serial_number, BarcodeFormat.CODE_128, barcodeWidth, barcodeHeight)
            val barcodeBitmap = Bitmap.createBitmap(barcodeWidth, barcodeHeight, Bitmap.Config.ARGB_8888)
            for (y in 0 until barcodeHeight) {
                for (x in 0 until barcodeWidth) {
                    barcodeBitmap.setPixel(x, y, if (barcodeMatrix[x, y]) Color.BLACK else Color.WHITE)
                }
            }
            canvas.drawBitmap(barcodeBitmap, barcodeX.toFloat(), barcodeY.toFloat(), null)
        }

        return bitmap
    }

    suspend fun printItemLabel(
        printerManager: PrinterManager,
        item: Item,
        labelWidthMm: Int = 53,
        labelHeightMm: Int = 40,
        blackMarkEnabled: Boolean = true,
        unwindMm: Int = 40,
        printRetractMm: Int = 10
    ): Result<Unit> = printMutex.withLock {
        withContext(Dispatchers.IO) {
            try {
                printerManager.initDevice().onFailure { return@withContext Result.failure(it) }
                if (printRetractMm > 0) {
                    printerManager.setUnwindLength(mmToDots(printRetractMm))
                }
                val widthDots = mmToDots(labelWidthMm)
                val heightDots = mmToDots(labelHeightMm)
                val bitmap = buildItemLabelBitmap(item, widthDots, heightDots)
                printerManager.printImage(bitmap, 1)
                if (blackMarkEnabled) {
                    printerManager.addLineFeed(2)
                    printerManager.enableMarkDetection(true)
                } else {
                    printerManager.addLineFeed((unwindMm / 7).toInt().coerceAtLeast(2))
                }
                printerManager.commit()
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }

    suspend fun printContainerLabel(
        printerManager: PrinterManager,
        containerName: String,
        barcode: String? = null,
        blackMarkEnabled: Boolean = true,
        unwindMm: Int = 40
    ): Result<Unit> = printMutex.withLock {
        withContext(Dispatchers.IO) {
            try {
                printerManager.initDevice().onFailure { return@withContext Result.failure(it) }
                printerManager.setFontSize(24)
                printerManager.setBold(true)
                printerManager.addText("容器: $containerName\n")
                if (barcode != null) {
                    printerManager.setFontSize(18)
                    printerManager.setBold(false)
                    printerManager.addBarcode(barcode)
                }
                if (blackMarkEnabled) {
                    printerManager.addLineFeed(2)
                    printerManager.enableMarkDetection(true)
                } else {
                    printerManager.addLineFeed((unwindMm / 7).toInt().coerceAtLeast(2))
                }
                printerManager.commit()
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }
}
