// Node version: 16.20.2
// Package json dependencies

/*
  "dependencies": {
    "@thiagoelg/node-printer": "^0.6.2",
    "cors": "^2.8.5",
    "express": "^5.1.0",
    "iconv": "^3.0.1",
    "nodemon": "^3.1.10"
  }
*/

// || nvm -> nvm install 16.20.2


import express from 'express'
import cors from 'cors'
import printer from '@thiagoelg/node-printer'
import iconv from "iconv-lite";
import QRCode from 'qrcode'
import { createCanvas, loadImage } from 'canvas'
// Genera un QR y lo imprime en la impresora térmica

const ESC = "\x1B"
const GS = "\x1D"
const defaultPrinter = printer.getDefaultPrinterName()

function init() { return ESC + "@"; }
function cut() { return GS + "V" + "\x41" + "\x00"; }
function line(n = 1) { return "\n".repeat(n); }
function boldOn() { return ESC + "E" + "\x01"; }
function boldOff() { return ESC + "E" + "\x00"; }
function center() { return ESC + "a" + "\x01"; }
function centerOff() { return ESC + "a" + "\x00"; }
function left() { return ESC + "a" + "\x00"; }
function right() { return ESC + "a" + "\x02"; }

const app = express()
app.use(cors())
app.use(express.json())

const max = 30;

app.get('/printers', (req, res) => {
    res.send(200)
})

app.post('/digitalpowerstock/ticket/venta', (req, res) => {
    const { nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, vendedor, productos, total, adelanto } = req.body;

    ticketDPSTock(nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, vendedor, productos, total, adelanto)
    res.send(200)
})

app.post('/facturador/ticket/a', (req, res) => {
    const { comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, localidad, provincia, comprobante, cliente, fecha, qr_link, productos, total, subtotal } = req.body;
    ticketFacturadorA(comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, localidad, provincia, comprobante, cliente, fecha, qr_link, productos, total, subtotal)
    res.send(200)
})

app.post('/facturador/ticket/b', (req, res) => {
    const { comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total } = req.body;
    ticketFacturadorB(comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
    )
    res.send(200)
})

app.post('/facturador/ticket/c', (req, res) => {
    const { comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total } = req.body;
    ticketFacturadorC(comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
    )
    res.send(200)
})

async function printQRAsImage(link, moduleSize = 6) {
    try {
        const qrSize = moduleSize * 33;

        const qrBuffer = await QRCode.toBuffer(link, {
            width: qrSize,
            margin: 2,
            errorCorrectionLevel: 'M',
            type: 'png'
        });

        const img = await loadImage(qrBuffer);
        const canvas = createCanvas(img.width, img.height);
        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0);

        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        const width = canvas.width;
        const height = canvas.height;
        const widthBytes = Math.ceil(width / 8);

        const imageBytes = [];

        for (let y = 0; y < height; y++) {
            for (let x = 0; x < widthBytes; x++) {
                let byte = 0;
                for (let bit = 0; bit < 8; bit++) {
                    const pixelX = x * 8 + bit;
                    if (pixelX < width) {
                        const idx = (y * width + pixelX) * 4;
                        const brightness = imageData.data[idx];
                        if (brightness < 128) {
                            byte |= (1 << (7 - bit));
                        }
                    }
                }
                imageBytes.push(byte);
            }
        }

        const commands = [];

        // Centrar
        commands.push(Buffer.from([0x1B, 0x61, 0x01]));

        // GS v 0
        const xL = widthBytes & 0xFF;
        const xH = (widthBytes >> 8) & 0xFF;
        const yL = height & 0xFF;
        const yH = (height >> 8) & 0xFF;

        commands.push(Buffer.from([0x1D, 0x76, 0x30, 0x00, xL, xH, yL, yH]));
        commands.push(Buffer.from(imageBytes));

        commands.push(Buffer.from([0x0A, 0x0A]));
        commands.push(Buffer.from([0x1B, 0x61, 0x00]));

        return Buffer.concat(commands);

    } catch (error) {
        console.error('Error generando QR:', error);
        return Buffer.from('');
    }
}

export function removeAccents(str) {
    return String(str)
        .normalize('NFD')               // descompone letras + diacríticos
        .replace(/[\u0300-\u036f]/g, ''); // elimina marcas diacríticas
}

function printDoubleLine() {
    return left() + "============================================" + line();
}

function printDottedLine() {
    return left() + "--------------------------------------------" + line();
}

function printProducts(products) {
    let ticket = "";
    const lineWidth = 46;
    const priceWidth = 10;

    for (const producto of products) {
        const nombre = removeAccents(producto?.nombre);
        const cantidad = producto?.cantidad;
        const precio = `$${producto?.precio}`;

        // Construir la parte izquierda: "2 x Producto"
        const prefix = `${cantidad} x `;
        const leftPart = `${prefix}${nombre}`;

        // Calcular espacio disponible para el texto completo (sin considerar el precio aún)
        const availableSpace = lineWidth;

        if (leftPart.length <= lineWidth - precio.length - 1) {
            // Si cabe todo en una línea (con el precio)
            const spaces = lineWidth - leftPart.length - precio.length;
            const spacePadding = " ".repeat(Math.max(0, spaces));
            ticket += left() + leftPart + spacePadding + precio + line();
        } else {
            // Dividir el texto en líneas
            const lines = [];
            let remainingText = leftPart;
            const indentation = " ".repeat(prefix.length);

            // Primera línea sin indentación
            lines.push(remainingText.substring(0, lineWidth));
            remainingText = remainingText.substring(lineWidth);

            // Líneas intermedias con indentación
            while (remainingText.length > lineWidth - indentation.length - precio.length - 1) {
                const lineText = indentation + remainingText.substring(0, lineWidth - indentation.length);
                lines.push(lineText);
                remainingText = remainingText.substring(lineWidth - indentation.length);
            }

            // Última línea con el precio
            const lastLineText = indentation + remainingText;
            const spaces = lineWidth - lastLineText.length - precio.length;
            const spacePadding = " ".repeat(Math.max(0, spaces));
            lines.push(lastLineText + spacePadding + precio);

            // Agregar todas las líneas al ticket
            for (const _line of lines) {
                ticket += left() + _line + line();
            }
        }

        if (Number(producto.descuento) > 0) {
            ticket += left() + `${producto.descuento}% OFF` + line();
        }

        if (producto?.observaciones?.length > 0) {
            const obs = removeAccents(producto.observaciones);
            ticket += "Observaciones: " + obs + line();
        }
        ticket += printDottedLine();
    }

    return ticket;
}

function printBetween(leftText, rightText, lineWidth = 46) {
    // Asegurar que los valores sean strings
    const left = String(leftText);
    const right = String(rightText);

    // Calcular el espacio disponible para el texto izquierdo
    const availableSpace = lineWidth - right.length;

    // Si el texto izquierdo es muy largo, cortarlo
    let finalLeft = left;
    if (left.length > availableSpace) {
        finalLeft = left.substring(0, availableSpace - 3) + "...";
    }

    // Calcular espacios de relleno
    const spaces = lineWidth - finalLeft.length - right.length;
    const spacePadding = " ".repeat(Math.max(0, spaces));

    // Retornar la línea formateada
    return finalLeft + spacePadding + right;
}

async function ticketDPSTock(
    nombre, direccion, cp, localidad, provincia, pais,
    telefono, mensaje, comprobante, cliente, fecha,
    vendedor, productos, total, adelanto
) {

    try {
        const products = JSON.parse(productos);
        //const products = productos;
        let ticket = "";

        // Encabezado
        ticket += init();
        ticket += center();
        ticket += boldOn() + nombre + boldOff() + line();
        ticket += direccion + line();
        ticket += `(${cp}) ${localidad}` + line();
        ticket += `${provincia} ${pais}` + line();
        ticket += telefono + line();
        ticket += mensaje + line(3);

        // Datos comprobante
        ticket += left();
        ticket += `Nro comprobante: #${comprobante}` + line();
        ticket += `Cliente: ${cliente}` + line();
        ticket += `Fecha: ${fecha}` + line();
        ticket += `Vendedor: ${vendedor}` + line(2);

        ticket += printProducts(JSON.parse(productos))
        ticket += line();

        // Total
        ticket += line(2);
        //ticket += left() + `Total: $${total}` + line(3);
        ticket += boldOn();
        ticket += printBetween("Total: ", `$${total}`) + line(3);
        if (adelanto) {
            ticket += printBetween("Abonado: ", `$${adelanto}`) + line(3);
        }
        ticket += boldOff();

        // Corte
        ticket += cut();

        // Convertir a CP850 (acentos correctos)
        const data = iconv.encode(ticket, "CP850");

        // Enviar a la impresora
        printer.printDirect({
            data,
            printer: printer.getDefaultPrinterName(),
            type: "RAW",
            success: jobID => console.log("Trabajo enviado:", jobID),
            error: err => console.error("Error:", err)
        });

    } catch (error) {
        console.error("Error al imprimir:", error);
    }
}

async function ticketFacturadorA(
    comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, localidad, provincia, comprobante, cliente, fecha, qr_link, productos, total, subtotal
) {

    try {
        let ticket = "";

        // Encabezado
        ticket += init();
        ticket += center();
        ticket += boldOn() + comercio + boldOff() + line(2);
        ticket += centerOff();

        ticket += nombre + line();
        ticket += "CUIT: Nro: " + comercio_cuit + line();
        ticket += "Ing. Brutos: " + comercio_cuit + line();
        ticket += "Direccion: " + direccion + line();
        ticket += `${localidad} (${provincia})` + line();
        ticket += "Inicio de Actividades: " + inicio_actividades + line();
        ticket += condicion_iva_comercio + line();
        ticket += "PUNTO DE VENTA: " + punto_venta + line();

        ticket += printDoubleLine()

        ticket += "TICKET / FACTURA A" + line();
        ticket += "Nro: " + comprobante + line();
        ticket += "Fecha: " + fecha + line();

        ticket += printDoubleLine()

        ticket += "TIPO CLIENTE: " + cliente_condicion + line();
        ticket += "CLIENTE: " + cliente + line();
        ticket += "CUIT: " + cliente_cuit + line();
        ticket += "DIRECCION: " + cliente_direccion + line();

        ticket += printDoubleLine()

        ticket += printProducts(JSON.parse(productos)) + line()

        // Total
        ticket += printBetween("SUBTOTAL. IMP. NETO GRAVADO: ", `$${subtotal}`) + line();
        ticket += printBetween("IVA: ", `$${iva_contenido}`) + line();
        ticket += boldOn();
        ticket += printBetween("Total: ", `$${total}`) + line(2);
        ticket += boldOff();

        ticket += boldOn() + "TRANSPARENCIA FISCAL" + boldOff() + line();
        ticket += "Reg. Transp. Fiscal (Ley 27743)" + line()
        ticket += "CAE: " + cae + line()
        ticket += "Vencimiento CAE: " + vencimiento_cae + line()

        ticket += line(2) + center()

        // Convertir texto a CP850 ANTES del QR
        const textData = iconv.encode(ticket, "CP850");

        // Generar QR como Buffer binario
        const qr = (await printQRAsImage(qr_link, 12));

        // Agregar saltos y corte después del QR
        const footer = iconv.encode(line(1) + cut(), "CP850");

        // Combinar todo
        const data = Buffer.concat([textData, qr, footer]);

        // Enviar a la impresora
        printer.printDirect({
            data,
            printer: printer.getDefaultPrinterName(),
            type: "RAW",
            success: jobID => console.log("Trabajo enviado:", jobID),
            error: err => console.error("Error:", err)
        });

    } catch (error) {
        console.error("Error al imprimir:", error);
    }
}

async function ticketFacturadorB(
    comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
) {

    try {
        let ticket = "";

        // Encabezado
        ticket += init();
        ticket += center();
        ticket += boldOn() + comercio + boldOff() + line(2);
        ticket += centerOff();

        ticket += nombre + line();
        ticket += "CUIT: Nro: " + comercio_cuit + line();
        ticket += "Ing. Brutos: " + comercio_cuit + line();
        ticket += "Direccion: " + direccion + line();
        ticket += `${localidad} (${provincia})` + line();
        ticket += "Inicio de Actividades: " + inicio_actividades + line();
        ticket += condicion_iva_comercio + line();
        ticket += "PUNTO DE VENTA: " + punto_venta + line();

        ticket += printDoubleLine()

        ticket += "TICKET / FACTURA B" + line();
        ticket += "Nro: " + comprobante + line();
        ticket += "Fecha: " + fecha + line();

        ticket += printDoubleLine()

        ticket += "TIPO CLIENTE: " + cliente_condicion + line();
        ticket += "CLIENTE: " + cliente + line();
        ticket += "CUIT: " + cliente_cuit + line();
        ticket += "DIRECCION: " + cliente_direccion + line();

        ticket += printDoubleLine()

        ticket += printProducts(JSON.parse(productos)) + line()

        // Total
        ticket += boldOn();
        ticket += printBetween("Total: ", `$${total}`) + line(2);
        ticket += boldOff();

        ticket += boldOn() + "TRANSPARENCIA FISCAL" + boldOff() + line();
        ticket += "Reg. Transp. Fiscal (Ley 27743)" + line()
        ticket += "IVA CONTENIDO: $" + iva_contenido + line()
        ticket += "CAE: " + cae + line()
        ticket += "Vencimiento CAE: " + vencimiento_cae + line()

        ticket += line(2) + center()

        // Convertir texto a CP850 ANTES del QR
        const textData = iconv.encode(ticket, "CP850");

        // Generar QR como Buffer binario
        const qr = (await printQRAsImage(qr_link, 12));

        // Agregar saltos y corte después del QR
        const footer = iconv.encode(line(1) + cut(), "CP850");

        // Combinar todo
        const data = Buffer.concat([textData, qr, footer]);

        // Enviar a la impresora
        printer.printDirect({
            data,
            printer: printer.getDefaultPrinterName(),
            type: "RAW",
            success: jobID => console.log("Trabajo enviado:", jobID),
            error: err => console.error("Error:", err)
        });

    } catch (error) {
        console.error("Error al imprimir:", error);
    }
}

async function ticketFacturadorC(
    comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
) {

    try {
        let ticket = "";

        // Encabezado
        ticket += init();
        ticket += center();
        ticket += boldOn() + comercio + boldOff() + line(2);
        ticket += centerOff();

        ticket += nombre + line();
        ticket += "CUIT: Nro: " + comercio_cuit + line();
        ticket += "Ing. Brutos: " + comercio_cuit + line();
        ticket += "Direccion: " + direccion + line();
        ticket += `${localidad} (${provincia})` + line();
        ticket += "Inicio de Actividades: " + inicio_actividades + line();
        ticket += condicion_iva_comercio + line();
        ticket += "PUNTO DE VENTA: " + punto_venta + line();

        ticket += printDoubleLine()

        ticket += "TICKET / FACTURA C" + line();
        ticket += "Nro: " + comprobante + line();
        ticket += "Fecha: " + fecha + line();

        ticket += printDoubleLine()

        ticket += "TIPO CLIENTE: " + cliente_condicion + line();
        ticket += "CLIENTE: " + cliente + line();
        ticket += "CUIT: " + cliente_cuit + line();
        ticket += "DIRECCION: " + cliente_direccion + line();

        ticket += printDoubleLine()

        ticket += printProducts(JSON.parse(productos)) + line()

        // Total

        ticket += boldOn();
        ticket += printBetween("Total: ", `$${total}`);
        ticket += boldOff();
        ticket += line(2);

        ticket += boldOn() + "TRANSPARENCIA FISCAL" + boldOff() + line();
        ticket += "Reg. Transp. Fiscal (Ley 27743)" + line()
        ticket += "CAE: " + cae + line()
        ticket += "Vencimiento CAE: " + vencimiento_cae + line()

        ticket += line(2) + center()

        // Convertir texto a CP850 ANTES del QR
        const textData = iconv.encode(ticket, "CP850");

        // Generar QR como Buffer binario
        const qr = (await printQRAsImage(qr_link, 12));

        // Agregar saltos y corte después del QR
        const footer = iconv.encode(line(1) + cut(), "CP850");

        // Combinar todo
        const data = Buffer.concat([textData, qr, footer]);

        // Enviar a la impresora
        printer.printDirect({
            data,
            printer: printer.getDefaultPrinterName(),
            type: "RAW",
            success: jobID => console.log("Trabajo enviado:", jobID),
            error: err => console.error("Error:", err)
        });

    } catch (error) {
        console.error("Error al imprimir:", error);
    }
}

app.listen(3030, () => {
    console.log("Servicio de impresion de tickets inicializado en el puerto 3030");
    // testQROnly();
})