package com.utopiaindustries.service;

import com.google.zxing.BarcodeFormat;
import com.itextpdf.io.font.constants.StandardFonts;
import com.itextpdf.io.image.ImageData;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.element.AreaBreak;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.AreaBreakType;
import com.itextpdf.layout.property.TextAlignment;
import com.utopiaindustries.dao.ctp.*;
import com.utopiaindustries.model.ctp.*;
import com.utopiaindustries.util.BarcodeUtils;
import com.utopiaindustries.util.StringUtils;
import com.zebra.sdk.comm.Connection;
import com.zebra.sdk.comm.TcpConnection;
import com.zebra.sdk.graphics.internal.ZebraImage;
import com.zebra.sdk.printer.ZebraPrinter;
import com.zebra.sdk.printer.ZebraPrinterFactory;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Image;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.net.Socket;
import java.nio.file.Path;
import java.nio.file.FileSystems;

import java.awt.*;
import java.awt.Font;
import java.io.*;

import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import com.google.zxing.EncodeHintType;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;

import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class BarcodeService {

    @Value("${ctp.printer.ipAdd}")
    private String ipAddress;

    @Value("${ctp.printer.port}")
    private int port;

    @Value("${ctp.printer.bundleIpAdd}")
    private String bundleIpAddress;

    @Value("${ctp.printer.bundlePort}")
    private int bundlePort;

    @Value("${ctp.printer.stitchQRPath}")
    private String stitchPath;

    @Value("${ctp.printer.bundlePath}")
    private String bundlePath;

    private final BundleDAO bundleDAO;
    private final JobCardDAO jobCardDAO;
    private final MasterBundleDAO masterBundleDAO;
    private final FinishedItemDAO finishedItemDAO;
    private final StitchingOfflineItemDAO stitchingOfflineItemDAO;
    private final InventoryAccountDAO inventoryAccountDAO;
    private final JobCardItemDAO jobCardItemDAO;

    public BarcodeService(BundleDAO bundleDAO, JobCardDAO jobCardDAO, MasterBundleDAO masterBundleDAO, FinishedItemDAO finishedItemDAO, StitchingOfflineItemDAO stitchingOfflineItemDAO, InventoryAccountDAO inventoryAccountDAO, JobCardItemDAO jobCardItemDAO) {
        this.bundleDAO = bundleDAO;
        this.jobCardDAO = jobCardDAO;
        this.masterBundleDAO = masterBundleDAO;
        this.finishedItemDAO = finishedItemDAO;
        this.stitchingOfflineItemDAO = stitchingOfflineItemDAO;
        this.inventoryAccountDAO = inventoryAccountDAO;
        this.jobCardItemDAO = jobCardItemDAO;
    }

    /*
     *  generate barcodes here
     * */
    public void generateBarcodes(List<Long> ids, String artifactType) throws Exception {
        String size = BarcodeStickerSize.SIZE_4_X_7.name();
        BarcodeStickerSize stickerSize = BarcodeStickerSize.getSize(size);

        List<? extends InventoryArtifact> list = new ArrayList<>();
        if (StringUtils.compare(artifactType, Bundle.class.getSimpleName())) {
            list = bundleDAO.findByIds(ids);
            getBarcodeImages(list, stickerSize, artifactType);
        } else if (StringUtils.compare(artifactType, MasterBundle.class.getSimpleName())) {
            list = masterBundleDAO.findByIds(ids);
            getBarcodeImages(list, stickerSize, artifactType);
        } else if (StringUtils.compare(artifactType, FinishedItem.class.getSimpleName())) {
            String sizeForStitchItem = BarcodeStickerSize.SIZE_1_X_2.name();
            BarcodeStickerSize stickerSizeForStitchItem = BarcodeStickerSize.getSize(sizeForStitchItem);
            list = stitchingOfflineItemDAO.findByIds(ids);
            getBarcodeImagesForStitchItems(list, stickerSizeForStitchItem);
        }
    }

    public void getBarcodeImages(List<? extends InventoryArtifact> artifacts,
                                 BarcodeStickerSize stickerSize,
                                 String artifactType) throws Exception {
        int pageWidth = 900; // A4 Width
        int pageHeight = 1200; // A4 Height
        int rows = 3;
        int cols = 2;
        int totalStickersPerPage = rows * cols;
        int totalPages = (int) Math.ceil((double) artifacts.size() / totalStickersPerPage); // Total required pages
        List<BufferedImage> bufferedImages = new ArrayList<>();
        for (int page = 0; page < totalPages; page++) {
            // Create a Blank A4 Page Image
            BufferedImage a4Image = new BufferedImage(pageWidth, pageHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = a4Image.createGraphics();
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setColor(Color.WHITE);
            g2d.fillRect(0, 0, pageWidth, pageHeight);
            g2d.setColor(Color.BLACK);

            Font barcodeFont = new Font("Helvetica", Font.BOLD, stickerSize.getTextSize()+8);
            Font detailFont = new Font("Helvetica", Font.PLAIN, stickerSize.getTextSize()+3);

            for (int i = 0; i < totalStickersPerPage; i++) {
                int artifactIndex = page * totalStickersPerPage + i;
                if (artifactIndex >= artifacts.size()) break;
                InventoryArtifact artifact = artifacts.get(artifactIndex);
                int row = i / cols;
                int col = i % cols;

                int x = col * (pageWidth / cols);
                int y = row * (pageHeight / rows);

                Font stickerFont = new Font("Helvetica", Font.BOLD, stickerSize.getTextSize() + 10);
                g2d.setFont(stickerFont);
                // Draw Sticker Border
                g2d.drawRect(x, y, pageWidth / cols, pageHeight / rows);

                this.drawCenteredText(g2d, artifactType.equals("Bundle")?"Sub-Bundle":"Master-Bundle", detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop()-20,stickerSize.getMarginLeft()-165);

                // Draw SKU
                g2d.setFont(barcodeFont);
                String sku = artifact.getSku();
                FontMetrics fontMetrics = g2d.getFontMetrics();
                int textWidth = fontMetrics.stringWidth(sku);
                g2d.drawString(sku, x + ((pageWidth / cols) - textWidth) / 2, y + fontMetrics.getAscent()+ stickerSize.getMarginTop()+10);

                // Generate Barcode Image
                g2d.setFont(barcodeFont);
                byte[] imgBytes = BarcodeUtils.getBarcodeImageByteArray(
                        artifact.getBarcode(), BarcodeFormat.CODE_128, stickerSize.getImageWidthBarcode(), stickerSize.getImageHeightBarcode());

                BufferedImage barcodeImage = ImageIO.read(new ByteArrayInputStream(imgBytes));
                int originalBarcodeWidth = barcodeImage.getWidth();
                int originalBarcodeHeight = barcodeImage.getHeight();


                double scaleX = (double)(pageWidth / cols) / originalBarcodeWidth;
                double scaleY = (double)(pageHeight / rows) / originalBarcodeHeight;
                double scaleFactor = Math.min(scaleX, scaleY);

                int scaledWidth = (int)(originalBarcodeWidth * scaleFactor) - 30;
                int scaledHeight = (int)(originalBarcodeHeight * scaleFactor) - 10;

                BufferedImage scaledBarcodeImage = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2dBarcode = scaledBarcodeImage.createGraphics();
                g2dBarcode.drawImage(barcodeImage, 0, 0, scaledWidth, scaledHeight, null);
                g2dBarcode.dispose();

                int barcodeX = x + ((pageWidth / cols) - scaledBarcodeImage.getWidth()) / 2;
                int barcodeY = y + fontMetrics.getAscent() + stickerSize.getMarginTop() + 20;
                g2d.drawImage(scaledBarcodeImage, barcodeX, barcodeY, null);

                //barcode text
                g2d.setFont(barcodeFont);
                String barcode = artifact.getBarcode();
                FontMetrics barcodeFontMatrix = g2d.getFontMetrics();
                int barcodeText = fontMetrics.stringWidth(barcode);
                g2d.drawString(barcode, (x + ((pageWidth / cols) - barcodeText) / 2), y + barcodeFontMatrix.getAscent() + stickerSize.getMarginTop() + 120);

                //draw item-id
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm");
                this.drawCenteredText(g2d, "Item-ID: "+artifact.getItemId(), detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop() + 150,stickerSize.getMarginLeft());
                this.drawCenteredText(g2d, "Created-By: "+artifact.getCreatedBy(), detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop() + 170,stickerSize.getMarginLeft());
                this.drawCenteredText(g2d, "Created-At: "+formatter.format(artifact.getCreatedAt()), detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop() + 190,stickerSize.getMarginLeft());

                //draw job-card details
                JobCard jobCard = jobCardDAO.find(artifact.getJobCardId());

                this.drawCenteredText(g2d, "Job-Card: " + jobCard.getCode(), detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop() + 210, stickerSize.getMarginLeft() );
                this.drawCenteredText(g2d, "Purchase-Order: " + jobCard.getPurchaseOrderId(), detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop() + 230, stickerSize.getMarginLeft());
                this.drawCenteredText(g2d, "Lot-Number: " + jobCard.getLotNumber(), detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop() + 250, stickerSize.getMarginLeft());
                if((artifactType.equals("Bundle"))){
                    JobCardItem jobCardItem = jobCardItemDAO.findByCardId(jobCard.getId()).get(0);
                    InventoryAccount inventoryAccount = inventoryAccountDAO.find(jobCardItem.getAccountId());
                    this.drawCenteredText(g2d, "Pieces: " + artifact.getWrapQuantity(), detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop() + 270, stickerSize.getMarginLeft());
                    this.drawCenteredText(g2d, "Cutting-Account: " + inventoryAccount.getTitle() , detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop() + 290, stickerSize.getMarginLeft());
                }else {
                    List<Bundle> bundles = bundleDAO.findByMasterId(artifact.getId());
                    String concatenatedIds = bundles.stream()
                            .map(bundle -> String.valueOf(bundle.getId()))
                            .collect(Collectors.joining("\\"));
                    int totalWrappedQuantity = bundles.stream()
                            .mapToInt(e->e.getWrapQuantity().intValue())
                            .sum();
                    this.drawCenteredText(g2d, "Total-Pieces: " + totalWrappedQuantity, detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop() + 270, stickerSize.getMarginLeft());
                    this.drawCenteredText(g2d, "Sub-Bundles: " + concatenatedIds, detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop() + 290, stickerSize.getMarginLeft());
                }

                this.drawCenteredText(g2d, String.valueOf(artifact.getId()), detailFont, x, y, pageWidth, cols, stickerSize.getMarginTop() + 330, 0);
            }
            g2d.dispose();
            bufferedImages.add(a4Image);
        }
        saveA4ImageToPDF(bufferedImages,bundlePath);
        try {
            printPDF(bundleIpAddress, bundlePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void printPDF(String printerIP, String pdfFilePath) throws Exception {
        File pdfFile = new File(pdfFilePath);
        FileInputStream fis = new FileInputStream(pdfFile);

        Socket printerSocket = new Socket(printerIP, port);
        OutputStream out = printerSocket.getOutputStream();

        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = fis.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }
        out.flush();
        out.close();
        fis.close();
        printerSocket.close();
    }

    public void saveA4ImageToPDF(List<BufferedImage> a4Images, String outputFilePath) throws Exception {
        PdfWriter writer = new PdfWriter(outputFilePath);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document document = new Document(pdfDoc, PageSize.A4);
        document.setMargins(0, 0, 0, 0);

        for (int i = 0; i < a4Images.size(); i++) {
            if (i > 0) {
                document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(a4Images.get(i), "png", baos);
            byte[] imageBytes = baos.toByteArray();
            ImageData imageData = ImageDataFactory.create(imageBytes);
            Image image = new Image(imageData);

            // Scale and position correctly
            image.scaleToFit(pdfDoc.getDefaultPageSize().getWidth(), pdfDoc.getDefaultPageSize().getHeight());
            image.setFixedPosition(0, 30);
            document.add(image);
        }
        document.close();
    }

    private void drawCenteredText(Graphics2D g2d, String text, Font font, int x, int y, int pageWidth, int cols, int marginTop, int marginLeft) {
        g2d.setFont(font);
        FontMetrics fontMetrics = g2d.getFontMetrics();
        int adjustedX = (x + ((pageWidth / cols) ) / 2) - marginLeft;
        int adjustedY = y + fontMetrics.getAscent() + marginTop;
        g2d.drawString(text, adjustedX, adjustedY);
    }

    public void getBarcodeImagesForStitchItems(List<? extends InventoryArtifact> artifacts,
                                               BarcodeStickerSize stickerSize) throws Exception {

        Path pdfPath = FileSystems.getDefault().getPath(stitchPath);
        PdfWriter writer = new PdfWriter(pdfPath.toFile());
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document document = new Document(pdfDoc);
        pdfDoc.setDefaultPageSize(new PageSize(stickerSize.getWidth(), stickerSize.getHeight()));

        for (InventoryArtifact artifact : artifacts) {
            JobCard jobCard = jobCardDAO.find(artifact.getJobCardId());

            PdfPage page = pdfDoc.addNewPage();
            PdfCanvas canvas = new PdfCanvas(page);
            canvas.setFillColor(ColorConstants.WHITE);
            canvas.rectangle(0, 0, stickerSize.getWidth(), stickerSize.getHeight());
            canvas.fill();

            // Draw QR Code on Left Side
            byte[] imgBytes = generateQRCodeImageByteArray(artifact.getBarcode(), stickerSize.getImageWidthBarcode(), stickerSize.getImageHeightBarcode());
            Image qrCodeImage = new Image(ImageDataFactory.create(imgBytes));

            float qrY =stickerSize.getMarginLeft()+50 ;
            float qrX = stickerSize.getMarginTop();

            qrCodeImage.setFixedPosition(qrX - 16, qrY-35);
            document.add(qrCodeImage);

            float textX = stickerSize.getMarginLeft() + 40;
            float textY = stickerSize.getMarginTop() + 40;

            String sku = artifact.getSku();
            String jobCardCode = jobCard.getCode();
            String combinedText = sku + " \\ " + jobCardCode + " \\ " + artifact.getBundleId();
            combinedText = combinedText.replaceAll("\\s+", "");

            int maxLength = 12;
            int start = 0;
            int lineCount = 0;
            List<String> lines = new ArrayList<>();

            while (start < combinedText.length() && lineCount < 5) {
                int end = Math.min(start + maxLength, combinedText.length());
                String part = combinedText.substring(start, end);

                if (part.matches(".*[^a-zA-Z0-9 ].*")) {
                    if(combinedText.length() - start<=17 && combinedText.length() - start>0){
                        end = Math.min(start + maxLength + 5, combinedText.length());
                        part = combinedText.substring(start, end);
                    }else {
                        end = Math.min(start + maxLength + 3, combinedText.length());
                        part = combinedText.substring(start, end);
                    }
                }
                part = part.replaceAll("\\s+", "");
                lines.add(part);
                start = end;
                lineCount++;
            }


            float labelWidth = 67.69f;
            float textBoxWidth = 120f;
            float textY1 = textY-23;
            float textXCenter = textX + (labelWidth / 2) - (textBoxWidth / 2)-50;

            for (String line : lines) {
                Paragraph rotatedText = new Paragraph(line)
                        .setFontColor(ColorConstants.BLACK)
                        .setFontSize(stickerSize.getTextSize()+2)
                        .setTextAlignment(TextAlignment.CENTER)
                        .setFixedPosition(textXCenter, textY1-13, textBoxWidth);

                document.add(rotatedText);
                textY1 -= 6;
            }

            PdfFont font = PdfFontFactory.createFont(StandardFonts.COURIER);
            String id = String.valueOf(artifact.getId());
            float textSize = stickerSize.getTextSize() + 6;
            float charWidth = textSize * 0.6f;
            float idWidth = id.length() * charWidth;
            float xCentered = (labelWidth / 2f) - (idWidth / 2f);

            document.add(new Paragraph(id)
                    .setFont(font)
                    .setBold()
                    .setFontColor(ColorConstants.BLACK)
                    .setFontSize(textSize)
                    .setFixedPosition(xCentered, textY + 18, 100));


            float dottedLine = textY - 65;
            for(int i= 0 ;i<16;i++){
                document.add(new Paragraph("|")
                        .setFontSize(stickerSize.getTextSize())
                        .setFontColor(ColorConstants.BLACK)
                        .setBold()
                        .setRotationAngle(-Math.PI / 2)
                        .setTextAlignment(TextAlignment.LEFT)
                        .setFixedPosition(dottedLine,textX+55, 100));

                dottedLine += 7;
            }

            float dottedLine2 = textY - 65;
            for(int i= 0 ;i<16;i++){
                document.add(new Paragraph("|")
                        .setFontSize(stickerSize.getTextSize())
                        .setFontColor(ColorConstants.BLACK)
                        .setBold()
                        .setRotationAngle(-Math.PI / 2)
                        .setTextAlignment(TextAlignment.LEFT)
                        .setFixedPosition(dottedLine2,textX+90, 100));

                dottedLine2 += 7;
            }

            if (!artifact.equals(artifacts.get(artifacts.size() - 1))) {
                document.add(new AreaBreak());
            }
        }

        if (pdfDoc.getNumberOfPages() > artifacts.size()) {
            pdfDoc.removePage(pdfDoc.getNumberOfPages());
        }

        document.close();
        sendPdfToZebraPrinter(pdfPath.toFile());
    }

    public void sendPdfToZebraPrinter(File pdfFile) throws Exception {
        PDDocument pdDocument = PDDocument.load(pdfFile);
        PDFRenderer renderer = new PDFRenderer(pdDocument);
        for (int pageIndex = 0; pageIndex < pdDocument.getNumberOfPages(); pageIndex++) {
            BufferedImage pageImage = renderer.renderImageWithDPI(pageIndex, 320);
            printLabel(pageImage);
        }
        pdDocument.close();
    }

    public byte[] generateQRCodeImageByteArray(String data, int width, int height) {
        try {
            QRCodeWriter qrCodeWriter = new QRCodeWriter();
            Map<EncodeHintType, Object> hintMap = new HashMap<>();
            hintMap.put(EncodeHintType.MARGIN, 0);
            BitMatrix bitMatrix = qrCodeWriter.encode(data, BarcodeFormat.QR_CODE, width, height, hintMap);
            BufferedImage qrImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            qrImage.createGraphics().fillRect(0, 0, width, height);

            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    qrImage.setRGB(x, y, bitMatrix.get(x, y) ? 0x000000 : 0xFFFFFF); // Black and white
                }
            }

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(qrImage, "PNG", baos);
            return baos.toByteArray();
        } catch (WriterException | IOException e) {
            e.printStackTrace();
            return new byte[0];
        }
    }

    public void printLabel(BufferedImage bufferedImage) throws Exception {
        Connection connection = new TcpConnection(ipAddress, port);
        connection.open();
        ZebraPrinter printer = ZebraPrinterFactory.getInstance(connection);
        ZebraImage zebraImage = new ZebraImage(bufferedImage);
        printer.printImage(zebraImage, 0, 0, 0, 0, false);
        connection.close();
    }
}