import { FastifyInstance } from "fastify"
import { PNG } from "pngjs"
import { Utils } from "@mmote/niimbluelib"
import bwipjs from "bwip-js"
import Sharp from "sharp"

import { tenants } from "../../db/schema"
import { eq } from "drizzle-orm"

export const useNextNumberRangeNumber = async (
    server: FastifyInstance,
    tenantId: number,
    numberRange: string
) => {
    const numberRangeFallbacks: Record<string, string> = {
        costEstimates: "quotes",
        packingSlips: "deliveryNotes",
        advanceInvoices: "invoices",
        cancellationInvoices: "invoices",
    }

    const [tenant] = await server.db
        .select()
        .from(tenants)
        .where(eq(tenants.id, tenantId))

    if (!tenant) {
        throw new Error(`Tenant ${tenantId} not found`)
    }

    const numberRanges = tenant.numberRanges || {}

    const resolvedNumberRange = numberRanges[numberRange]
        ? numberRange
        : numberRangeFallbacks[numberRange]

    if (!resolvedNumberRange || !numberRanges[resolvedNumberRange]) {
        throw new Error(`Number range '${numberRange}' not found`)
    }

    const current = numberRanges[resolvedNumberRange]

    const usedNumber =
        (current.prefix || "") +
        current.nextNumber +
        (current.suffix || "")

    const updatedRanges = {
        // @ts-ignore
        ...numberRanges,
        [resolvedNumberRange]: {
            ...current,
            nextNumber: current.nextNumber + 1,
        },
    }

    await server.db
        .update(tenants)
        .set({ numberRanges: updatedRanges })
        .where(eq(tenants.id, tenantId))

    return { usedNumber }
}

export async function encodeBase64ToNiimbot(base64Png: string, printDirection: "top" | "left" = "top") {
    const buffer = Buffer.from(base64Png, "base64")
    const png = PNG.sync.read(buffer)

    const { width, height, data } = png
    const cols = printDirection === "left" ? height : width
    const rows = printDirection === "left" ? width : height
    const rowsData: any[] = []

    if (cols % 8 !== 0) throw new Error("Column count must be multiple of 8")

    for (let row = 0; row < rows; row++) {
        let isVoid = true
        let blackPixelsCount = 0
        const rowData = new Uint8Array(cols / 8)

        for (let colOct = 0; colOct < cols / 8; colOct++) {
            let pixelsOctet = 0
            for (let colBit = 0; colBit < 8; colBit++) {
                const x = printDirection === "left" ? row : colOct * 8 + colBit
                const y = printDirection === "left" ? height - 1 - (colOct * 8 + colBit) : row
                const idx = (y * width + x) * 4
                const lum = 0.299 * data[idx] + 0.587 * data[idx + 1] + 0.114 * data[idx + 2]
                const isBlack = lum < 128
                if (isBlack) {
                    pixelsOctet |= 1 << (7 - colBit)
                    isVoid = false
                    blackPixelsCount++
                }
            }
            rowData[colOct] = pixelsOctet
        }

        const newPart = {
            dataType: isVoid ? "void" : "pixels",
            rowNumber: row,
            repeat: 1,
            rowData: isVoid ? undefined : rowData,
            blackPixelsCount,
        }

        if (rowsData.length === 0) {
            rowsData.push(newPart)
        } else {
            const last = rowsData[rowsData.length - 1]
            let same = newPart.dataType === last.dataType
            if (same && newPart.dataType === "pixels") {
                same = Utils.u8ArraysEqual(newPart.rowData, last.rowData)
            }
            if (same) last.repeat++
            else rowsData.push(newPart)

            if (row % 200 === 199) {
                rowsData.push({
                    dataType: "check",
                    rowNumber: row,
                    repeat: 0,
                    rowData: undefined,
                    blackPixelsCount: 0,
                })
            }
        }
    }

    return { cols, rows, rowsData }
}

function escapeXml(value: string) {
    return String(value)
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/\"/g, "&quot;")
        .replace(/'/g, "&apos;")
}

export async function generateLabel(context: any = {}, width = 584, height = 354) {
    const normalizedWidth = Math.ceil(Number(width) / 8) * 8
    const normalizedHeight = Math.max(1, Number(height) || 203)

    const idFont = Math.max(24, Math.round(normalizedHeight * 0.125))
    const nameFont = Math.max(17, Math.round(normalizedHeight * 0.078))
    const customerFont = Math.max(14, Math.round(normalizedHeight * 0.06))
    const serialFont = Math.max(12, Math.round(normalizedHeight * 0.052))

    const labelId = context.customerInventoryId || context.datamatrix || context.id || "N/A"
    const labelName = context.name || context.text || "Kundeninventarartikel"
    const customerName = context.customerName || ""
    const serial = context.serialNumber ? `SN: ${context.serialNumber}` : ""
    const nameLine1 = String(labelName).slice(0, 30)
    const nameLine2 = String(labelName).slice(30, 60)

    const dataMatrixPng = await bwipjs.toBuffer({
        bcid: "datamatrix",
        text: String(labelId),
        scale: normalizedWidth >= 560 ? 7 : 5,
        includetext: false,
    })
    const dataMatrixMeta = await Sharp(dataMatrixPng).metadata()
    const dataMatrixWidth = dataMatrixMeta.width || 0
    const dataMatrixHeight = dataMatrixMeta.height || 0
    const dmLeft = Math.max(8, normalizedWidth - dataMatrixWidth - 28)
    const dmTop = Math.max(8, Math.floor((normalizedHeight - dataMatrixHeight) / 2))
    const textMaxWidth = Math.max(120, dmLeft - 20)

    const textSvg = `
<svg width="${normalizedWidth}" height="${normalizedHeight}" xmlns="http://www.w3.org/2000/svg">
  <rect width="100%" height="100%" fill="white"/>
  <text x="12" y="${Math.round(normalizedHeight * 0.15)}" font-size="${idFont}" font-family="Arial, Helvetica, sans-serif" font-weight="700" fill="black">${escapeXml(String(labelId).slice(0, 26))}</text>
  <text x="12" y="${Math.round(normalizedHeight * 0.29)}" font-size="${nameFont}" font-family="Arial, Helvetica, sans-serif" fill="black">${escapeXml(nameLine1)}</text>
  <text x="12" y="${Math.round(normalizedHeight * 0.37)}" font-size="${nameFont}" font-family="Arial, Helvetica, sans-serif" fill="black">${escapeXml(nameLine2)}</text>
  <text x="12" y="${Math.round(normalizedHeight * 0.49)}" font-size="${customerFont}" font-family="Arial, Helvetica, sans-serif" fill="black">${escapeXml(String(customerName).slice(0, 40))}</text>
  <text x="12" y="${Math.round(normalizedHeight * 0.58)}" font-size="${serialFont}" font-family="Arial, Helvetica, sans-serif" fill="black">${escapeXml(String(serial).slice(0, 42))}</text>
  <rect x="0" y="0" width="${textMaxWidth}" height="${normalizedHeight}" fill="none"/>
</svg>`.trim()

    const final = await Sharp({
        create: {
            width: normalizedWidth,
            height: normalizedHeight,
            channels: 3,
            background: { r: 255, g: 255, b: 255 },
        },
    })
        .composite([
            { input: Buffer.from(textSvg), top: 0, left: 0 },
            { input: dataMatrixPng, top: dmTop, left: dmLeft },
        ])
        .png()
        .toBuffer()

    return final.toString("base64")
}
