import PDFDocument from "pdfkit";
import type { Response } from "express";
import http from "http";
import https from "https";
import fs from "fs";
import path from "path";
import QRCode from "qrcode";
import { PdfHeader } from "./components/PdfHeader.js";
import { fileURLToPath } from "url";
import { ShopService } from "../../services/ShopService.js";
import { UpiService } from "../../services/UpiService.js";
import prisma from "../../config/prisma.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const FONTS_DIR = path.join(__dirname, "fonts");

const COLORS = {
    primary: "#0F5132",
    accent: "#198754",
    text: "#212529",
    muted: "#6C757D",
    border: "#DEE2E6",
    rowAlt: "#F8F9FA",
    white: "#FFFFFF",
};

const PAGE_MARGIN = 40;
const FOOTER_HEIGHT = 180;

export class FastBillingInvoicePdf {
    static async generate(
        fastBilling: any,
        res: Response,
        options: { lang?: string | undefined; printSign?: boolean | undefined } = { lang: "en", printSign: false }
    ) {
        const doc = new PDFDocument({
            size: "A4",
            margin: PAGE_MARGIN,
            bufferPages: true,
        });

        // Register custom font for vegetable names when Hindi/Tamil is requested
        let vegFont = "Helvetica";
        const lang = options.lang || "en";
        if (lang === "hi" && fs.existsSync(path.join(FONTS_DIR, "NotoSansDevanagari-Regular.ttf"))) {
            doc.registerFont("VegFont", path.join(FONTS_DIR, "NotoSansDevanagari-Regular.ttf"));
            vegFont = "VegFont";
        } else if (lang === "ta" && fs.existsSync(path.join(FONTS_DIR, "NotoSansTamil-Regular.ttf"))) {
            doc.registerFont("VegFont", path.join(FONTS_DIR, "NotoSansTamil-Regular.ttf"));
            vegFont = "VegFont";
        } else if (lang === "kn" && fs.existsSync(path.join(FONTS_DIR, "NotoSansKannada-Regular.ttf"))) {
            doc.registerFont("VegFont", path.join(FONTS_DIR, "NotoSansKannada-Regular.ttf"));
            vegFont = "VegFont";
        } else if (lang === "ml" && fs.existsSync(path.join(FONTS_DIR, "Manjari-Regular.ttf"))) {
            doc.registerFont("VegFont", path.join(FONTS_DIR, "Manjari-Regular.ttf"));
            vegFont = "VegFont";
        } else if (lang === "te" && fs.existsSync(path.join(FONTS_DIR, "NotoSansTelugu-Regular.ttf"))) {
            doc.registerFont("VegFont", path.join(FONTS_DIR, "NotoSansTelugu-Regular.ttf"));
            vegFont = "VegFont";
        }

        const shop = await new ShopService().getShop();
        const upis = await new UpiService().getUpis();
        const upi = upis.length > 0 ? upis[0] : null;

        let leftQrBuf: Buffer | null = null;
        let rightQrBuf: Buffer | null = null;
        if (upi?.upiId && shop?.shopName) {
            const anyAmountUrl = `upi://pay?pa=${upi.upiId}&pn=${encodeURIComponent(shop.shopName)}`;
            leftQrBuf = await QRCode.toBuffer(anyAmountUrl, { margin: 1, type: "png" });

            const grandTotal = Number(fastBilling?.totalAmount ?? 0).toFixed(2);
            const billAmountUrl = `upi://pay?pa=${upi.upiId}&pn=${encodeURIComponent(shop.shopName)}&am=${grandTotal}`;
            rightQrBuf = await QRCode.toBuffer(billAmountUrl, { margin: 1, type: "png" });
        }

        let authSignBuf: Buffer | null = null;
        if (options.printSign && shop?.authorizedSign) {
            try {
                const signPath = path.join(process.cwd(), "uploads", "signatures", shop.authorizedSign);
                authSignBuf = await this.fetchImageBuffer(signPath);
            } catch (err) {
                console.error("[FastBillingInvoicePdf] Failed to fetch authorizedSign:", shop.authorizedSign, err);
            }
        }

        // Build vegetable translations map if translation is requested
        let vegTranslations = new Map<string, { tamilName?: string | null; hindiName?: string | null; kannadaName?: string | null; malayalamName?: string | null; teluguName?: string | null }>();
        if (["ta", "hi", "kn", "ml", "te"].includes(lang)) {
            try {
                const vegs = await prisma.vegetable.findMany({
                    select: { name: true, tamilName: true, hindiName: true, kannadaName: true, malayalamName: true, teluguName: true }
                });
                vegs.forEach((v) => {
                    vegTranslations.set(v.name.toLowerCase().trim(), {
                        tamilName: v.tamilName,
                        hindiName: v.hindiName,
                        kannadaName: v.kannadaName,
                        malayalamName: v.malayalamName,
                        teluguName: v.teluguName
                    });
                });
            } catch (err) {
                console.error("[FastBillingInvoicePdf] Failed to load vegetable translations:", err);
            }
        }

        res.setHeader("Content-Type", "application/pdf");
        res.setHeader(
            "Content-Disposition",
            `attachment; filename="fast-bill-${fastBilling.billNo ?? fastBilling.id}.pdf"`
        );
        res.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
        doc.pipe(res);

        const pageWidth = doc.page.width - PAGE_MARGIN * 2;

        // Displays "CREDIT SALES BILL" in header
        await PdfHeader.draw(doc, pageWidth, PAGE_MARGIN, "INVOICE");

        this.drawCustomerMeta(doc, fastBilling, pageWidth);
        this.drawItemsTable(doc, fastBilling, pageWidth, lang, vegFont, vegTranslations);
        this.drawFooter(doc, pageWidth, fastBilling, leftQrBuf, rightQrBuf, authSignBuf);

        doc.end();
    }

    private static async fetchImageBuffer(url: string): Promise<Buffer> {
        if (!url.startsWith("http")) {
            return fs.promises.readFile(path.resolve(url));
        }
        return new Promise((resolve, reject) => {
            const client = url.startsWith("https") ? https : http;
            client.get(url, (res: any) => {
                if (res.statusCode !== 200) {
                    return reject(new Error(`Failed to fetch image: ${res.statusCode}`));
                }
                const data: Buffer[] = [];
                res.on("data", (chunk: Buffer) => data.push(chunk));
                res.on("end", () => resolve(Buffer.concat(data)));
            }).on("error", reject);
        });
    }

    private static drawCustomerMeta(doc: PDFKit.PDFDocument, bill: any, pageWidth: number) {
        const boxY = doc.y;
        const boxHeight = 62;
        const rightColWidth = pageWidth * 0.4;
        const leftColWidth = pageWidth - rightColWidth;
        const boxX = PAGE_MARGIN;

        const dateSource = bill?.billDate ?? bill?.createdAt;
        const date = dateSource
            ? new Date(dateSource).toLocaleDateString("en-GB").replace(/\//g, "-")
            : "-";

        doc
            .rect(boxX, boxY, pageWidth, boxHeight)
            .lineWidth(1)
            .strokeColor(COLORS.border)
            .stroke();

        const dividerX = boxX + leftColWidth;
        doc
            .moveTo(dividerX, boxY)
            .lineTo(dividerX, boxY + boxHeight)
            .strokeColor(COLORS.border)
            .lineWidth(1)
            .stroke();

        const leftPad = 10;
        const leftWidth = leftColWidth - leftPad * 2;
        const customerName = bill?.customerName || "Customer";

        doc
            .font("Helvetica-Bold")
            .fontSize(10.5)
            .fillColor(COLORS.primary)
            .text(customerName, boxX + leftPad, boxY + (bill?.remarks ? 14 : 24), {
                width: leftWidth,
                lineBreak: false,
                ellipsis: true,
            });

        if (bill?.remarks) {
            doc
                .font("Helvetica")
                .fontSize(8.5)
                .fillColor(COLORS.muted)
                .text(`Remarks: ${bill.remarks}`, boxX + leftPad, doc.y + 4, {
                    width: leftWidth,
                    lineBreak: false,
                    ellipsis: true,
                });
        }

        const rightPad = 16;
        const labelX = dividerX + rightPad;
        const labelWidth = 56;
        const colonX = labelX + labelWidth;
        const colonWidth = rightColWidth - rightPad * 2 - labelWidth;

        const inlineRow = (y: number, label: string, value: string) => {
            doc
                .font("Helvetica-Bold")
                .fontSize(10)
                .fillColor(COLORS.primary)
                .text(label, labelX, y, { width: labelWidth, lineBreak: false });

            doc
                .font("Helvetica-Bold")
                .fontSize(10)
                .fillColor(COLORS.text)
                .text(`: ${value}`, colonX, y, { width: colonWidth, lineBreak: false, ellipsis: true });
        };

        inlineRow(boxY + 14, "Invoice No", `${bill?.billNo ?? bill?.id}`);
        inlineRow(boxY + 34, "Date", date);

        doc.y = boxY + boxHeight + 20;
    }

    private static drawItemsTable(
        doc: PDFKit.PDFDocument,
        bill: any,
        pageWidth: number,
        lang: string,
        vegFont: string,
        vegTranslations: Map<string, { tamilName?: string | null; hindiName?: string | null; kannadaName?: string | null; malayalamName?: string | null; teluguName?: string | null }>
    ) {
        const cols = {
            no: { x: PAGE_MARGIN, width: pageWidth * 0.06 },
            name: { x: PAGE_MARGIN + pageWidth * 0.06, width: pageWidth * 0.38 },
            bagBox: { x: PAGE_MARGIN + pageWidth * 0.44, width: pageWidth * 0.11 },
            kgsPcs: { x: PAGE_MARGIN + pageWidth * 0.55, width: pageWidth * 0.11 },
            rate: { x: PAGE_MARGIN + pageWidth * 0.66, width: pageWidth * 0.14 },
            total: { x: PAGE_MARGIN + pageWidth * 0.8, width: pageWidth * 0.2 },
        };

        const colBoundaries = [
            PAGE_MARGIN,
            cols.name.x,
            cols.bagBox.x,
            cols.kgsPcs.x,
            cols.rate.x,
            cols.total.x,
            PAGE_MARGIN + pageWidth,
        ];

        const MAX_ROWS_PER_PAGE = 15;
        const TOTAL_ROW_HEIGHT = 28;
        const MIN_ROW_HEIGHT = 16;
        const MAX_ROW_HEIGHT = 28;

        const drawRowGrid = (y: number, height: number) => {
            colBoundaries.forEach((x) => {
                doc
                    .moveTo(x, y)
                    .lineTo(x, y + height)
                    .strokeColor(COLORS.border)
                    .lineWidth(0.75)
                    .stroke();
            });
        };

        const drawTableHeader = (rowHeight: number) => {
            const y = doc.y;
            const textY = y + rowHeight / 2 - 5;

            drawRowGrid(y, rowHeight);

            doc
                .font("Helvetica-Bold")
                .fontSize(9)
                .fillColor(COLORS.primary)
                .text("SL#", cols.no.x, textY, { width: cols.no.width, align: "center" })
                .text("PRODUCT", cols.name.x + 6, textY, { width: cols.name.width - 6 })
                .text("Bag/Box", cols.bagBox.x, textY, { width: cols.bagBox.width, align: "center" })
                .text("Kgs/Pcs", cols.kgsPcs.x, textY, { width: cols.kgsPcs.width, align: "center" })
                .text("RATE", cols.rate.x, textY, { width: cols.rate.width - 8, align: "right" })
                .text("AMOUNT", cols.total.x, textY, { width: cols.total.width - 8, align: "right" });

            doc.y = y + rowHeight;

            doc
                .moveTo(PAGE_MARGIN, doc.y)
                .lineTo(PAGE_MARGIN + pageWidth, doc.y)
                .strokeColor(COLORS.primary)
                .lineWidth(1)
                .stroke();
        };

        const bottomLimit = doc.page.height - PAGE_MARGIN - FOOTER_HEIGHT;

        drawTableHeader(MAX_ROW_HEIGHT);

        const availableForItems = bottomLimit - doc.y - TOTAL_ROW_HEIGHT;
        const rowHeight = Math.min(
            MAX_ROW_HEIGHT,
            Math.max(MIN_ROW_HEIGHT, Math.floor(availableForItems / MAX_ROWS_PER_PAGE))
        );

        const items: any[] = bill?.items ?? [];
        let totalBags = 0;
        let totalKgs = 0;
        let totalAmount = 0;

        const drawItemRow = (item: any, index: number) => {
            const y = doc.y;
            const textY = y + rowHeight / 2 - 5;

            const defaultName = item?.vegetableName ?? "Item";
            let displayName = defaultName;
            let currentFont = vegFont;
            if (["ta", "hi", "kn", "ml", "te"].includes(lang)) {
                const tr = vegTranslations.get(defaultName.toLowerCase().trim());
                if (lang === "ta" && tr?.tamilName) displayName = tr.tamilName;
                if (lang === "hi" && tr?.hindiName) displayName = tr.hindiName;
                if (lang === "kn" && tr?.kannadaName) displayName = tr.kannadaName;
                if (lang === "ml" && tr?.malayalamName) displayName = tr.malayalamName;
                if (lang === "te" && tr?.teluguName) displayName = tr.teluguName;
            }
            if (displayName === defaultName) {
                currentFont = "Helvetica";
            }

            const extraVal = Number(item?.extraCharges || 0);
            if (extraVal > 0) {
                displayName += ` (+${this.formatCurrency(extraVal)} extra)`;
            }

            let bagBoxStr = "-";
            const bagsVal = Number(item?.numberOfBoxes || 0);
            if (bagsVal > 0) {
                bagBoxStr = `${bagsVal}`;
                totalBags += bagsVal;
            }

            let kgsPcsStr = "-";
            const qtyVal = Number(item?.quantity || 0);
            const thaarVal = Number(item?.numberOfThaar || 0);
            const piecesVal = Number(item?.numberOfPieces || 0);

            if (qtyVal > 0) {
                kgsPcsStr = qtyVal % 1 === 0 ? `${qtyVal}` : qtyVal.toFixed(2);
                totalKgs += qtyVal;
            } else if (thaarVal > 0) {
                kgsPcsStr = `${thaarVal}`;
                totalKgs += thaarVal;
            } else if (piecesVal > 0) {
                kgsPcsStr = `${piecesVal}`;
                totalKgs += piecesVal;
            }

            const rate = this.formatCurrency(item?.ratePerUnit);
            const amount = Number(item?.totalAmount ?? 0);
            totalAmount += amount;
            const total = this.formatCurrency(amount);

            if (index % 2 === 1) {
                doc.rect(PAGE_MARGIN, y, pageWidth, rowHeight).fill(COLORS.rowAlt);
            }

            drawRowGrid(y, rowHeight);

            // Serial number
            doc
                .font("Helvetica-Bold")
                .fontSize(9)
                .fillColor(COLORS.accent)
                .text(`${index + 1}`, cols.no.x, textY, { width: cols.no.width, align: "center" });

            // Vegetable / Product Name
            try {
                doc
                    .font(currentFont)
                    .fontSize(9)
                    .fillColor(COLORS.text);

                // Monkey-patch fontkit GPOSProcessor to fix Indic font crash
                try {
                    const gpos = (doc as any)._font?.font?._layoutEngine?.engine?.GPOSProcessor;
                    if (gpos && !gpos.__patched) {
                        const originalGetAnchor = gpos.getAnchor.bind(gpos);
                        gpos.getAnchor = function(anchor: any, ...args: any[]) {
                            if (!anchor) return null;
                            return originalGetAnchor(anchor, ...args);
                        };
                        gpos.__patched = true;
                    }
                } catch (e) {}

                doc.text(displayName, cols.name.x + 6, textY, { width: cols.name.width - 10, ellipsis: false });
            } catch (err) {
                try {
                    doc
                        .font(currentFont)
                        .fontSize(9)
                        .fillColor(COLORS.text)
                        .text(displayName, cols.name.x + 6, textY, { width: cols.name.width - 10, ellipsis: false, features: ['-mark', '-mkmk', '-kern', '-blwm', '-abvm', '-dist', '-curs'] as any });
                } catch (err2) {
                    doc
                        .font("Helvetica")
                        .fontSize(9)
                        .fillColor(COLORS.text)
                        .text(item.name || "Item", cols.name.x + 6, textY, { width: cols.name.width - 10, ellipsis: true });
                }
            }

            // Bag/Box, Kgs/Pcs, Rate, Amount
            doc
                .font("Helvetica")
                .fontSize(9)
                .fillColor(COLORS.text)
                .text(bagBoxStr, cols.bagBox.x, textY, { width: cols.bagBox.width, align: "center" })
                .text(kgsPcsStr, cols.kgsPcs.x, textY, { width: cols.kgsPcs.width, align: "center" })
                .text(rate, cols.rate.x, textY, { width: cols.rate.width - 8, align: "right" })
                .text(total, cols.total.x, textY, { width: cols.total.width - 8, align: "right" });

            doc.y = y + rowHeight;
        };

        for (let pageStart = 0; pageStart < Math.max(items.length, 1); pageStart += MAX_ROWS_PER_PAGE) {
            const isFirstPage = pageStart === 0;
            if (!isFirstPage) {
                doc.addPage();
                doc.y = PAGE_MARGIN;
                drawTableHeader(rowHeight);
            }

            const chunk = items.slice(pageStart, pageStart + MAX_ROWS_PER_PAGE);
            chunk.forEach((item, i) => drawItemRow(item, pageStart + i));

            const isLastPage = pageStart + MAX_ROWS_PER_PAGE >= items.length;
            if (isLastPage) {
                for (let i = chunk.length; i < MAX_ROWS_PER_PAGE; i++) {
                    drawRowGrid(doc.y, rowHeight);
                    doc.y += rowHeight;
                }
            }
        }

        // Total row
        const totalY = doc.y;
        drawRowGrid(totalY, TOTAL_ROW_HEIGHT);
        const totalTextY = totalY + TOTAL_ROW_HEIGHT / 2 - 5;

        doc
            .font("Helvetica-Bold")
            .fontSize(10)
            .fillColor(COLORS.text)
            .text("Total", cols.name.x + 6, totalTextY, { width: cols.name.width - 10 })
            .text(totalBags > 0 ? `${totalBags}` : "-", cols.bagBox.x, totalTextY, {
                width: cols.bagBox.width,
                align: "center",
            })
            .text(totalKgs > 0 ? (totalKgs % 1 === 0 ? `${totalKgs}` : totalKgs.toFixed(2)) : "-", cols.kgsPcs.x, totalTextY, {
                width: cols.kgsPcs.width,
                align: "center",
            })
            .text(this.formatCurrency(bill?.totalAmount ?? totalAmount), cols.total.x, totalTextY, {
                width: cols.total.width - 8,
                align: "right",
            });

        doc.y = totalY + TOTAL_ROW_HEIGHT;

        doc
            .moveTo(PAGE_MARGIN, doc.y)
            .lineTo(PAGE_MARGIN + pageWidth, doc.y)
            .strokeColor(COLORS.primary)
            .lineWidth(1)
            .stroke();

        doc.y += 16;
    }

    private static drawFooter(
        doc: PDFKit.PDFDocument,
        pageWidth: number,
        bill: any,
        leftQrBuf: Buffer | null,
        rightQrBuf: Buffer | null,
        authSignBuf: Buffer | null
    ) {
        const footerTop = doc.page.height - PAGE_MARGIN - FOOTER_HEIGHT;
        const qrSize = 80;

        // Top separator line
        doc
            .moveTo(PAGE_MARGIN, footerTop)
            .lineTo(PAGE_MARGIN + pageWidth, footerTop)
            .strokeColor(COLORS.border)
            .lineWidth(1)
            .stroke();

        const qrY = footerTop + 16;
        const leftQrX = PAGE_MARGIN;
        const rightQrX = PAGE_MARGIN + pageWidth - qrSize;

        // Left QR: Scan for Any Amount
        if (leftQrBuf) {
            doc.image(leftQrBuf, leftQrX, qrY, { width: qrSize, height: qrSize });
        } else {
            doc.rect(leftQrX, qrY, qrSize, qrSize).lineWidth(1).strokeColor(COLORS.border).stroke();
            doc
                .font("Helvetica")
                .fontSize(7)
                .fillColor(COLORS.muted)
                .text("QR", leftQrX, qrY + qrSize / 2 - 4, { width: qrSize, align: "center" });
        }

        doc
            .font("Helvetica")
            .fontSize(8)
            .fillColor(COLORS.muted)
            .text("Scan for Any Amount", leftQrX - 10, qrY + qrSize + 6, {
                width: qrSize + 20,
                align: "center",
            });

        // Right QR: Scan for Bill Amount
        if (rightQrBuf) {
            doc.image(rightQrBuf, rightQrX, qrY, { width: qrSize, height: qrSize });
        } else {
            doc.rect(rightQrX, qrY, qrSize, qrSize).lineWidth(1).strokeColor(COLORS.border).stroke();
            doc
                .font("Helvetica")
                .fontSize(7)
                .fillColor(COLORS.muted)
                .text("QR", rightQrX, qrY + qrSize / 2 - 4, { width: qrSize, align: "center" });
        }

        doc
            .font("Helvetica")
            .fontSize(8)
            .fillColor(COLORS.muted)
            .text("Scan for Bill Amount", rightQrX - 10, qrY + qrSize + 6, {
                width: qrSize + 20,
                align: "center",
            });

        // Centered Totals Block
        const grandTotal = Number(bill?.totalAmount ?? 0);
        const totalsX = leftQrX + qrSize + 14;
        const totalsWidth = rightQrX - totalsX - 14;
        let ty = qrY + 12;

        const totalsRow = (
            label: string,
            value: string,
            opts: { bold?: boolean; size?: number; color?: string } = {}
        ) => {
            const size = opts.size ?? 9;
            doc
                .font(opts.bold ? "Helvetica-Bold" : "Helvetica")
                .fontSize(size)
                .fillColor(opts.color ?? COLORS.text)
                .text(label, totalsX, ty, { width: totalsWidth * 0.5, lineBreak: false })
                .text(value, totalsX, ty, { width: totalsWidth, align: "right", lineBreak: false });
            ty += size + 8;
        };

        totalsRow("GRAND TOTAL:", this.formatCurrency(grandTotal), {
            bold: true,
            size: 12,
            color: COLORS.primary,
        });

        if (bill?.remarks) {
            ty += 4;
            totalsRow("Remarks:", bill.remarks, { size: 8.5, color: COLORS.muted });
        }

        // Signature blocks
        const sigLineY = Math.max(qrY + qrSize + 34, ty + 20);
        const sigLineWidth = 150;

        // Party's Signature (matching CREDIT SALES BILL format)
        doc
            .moveTo(leftQrX, sigLineY)
            .lineTo(leftQrX + sigLineWidth, sigLineY)
            .strokeColor(COLORS.border)
            .lineWidth(1)
            .stroke();
        doc
            .font("Helvetica-Bold")
            .fontSize(9)
            .fillColor(COLORS.text)
            .text("Party's Signature", leftQrX, sigLineY + 4, { width: sigLineWidth });

        // Authorised Signatory (matching CREDIT SALES BILL format)
        const rightSigX = PAGE_MARGIN + pageWidth - sigLineWidth + 10;

        if (authSignBuf) {
            const sigImgW = 80;
            const sigImgH = 30;
            const sigImgX = rightSigX + (sigLineWidth - sigImgW) / 2;
            const sigImgY = sigLineY - sigImgH - 4;
            doc.image(authSignBuf, sigImgX, sigImgY, { width: sigImgW, height: sigImgH });
        }

        doc
            .moveTo(rightSigX, sigLineY)
            .lineTo(rightSigX + sigLineWidth, sigLineY)
            .strokeColor(COLORS.border)
            .lineWidth(1)
            .stroke();
        doc
            .font("Helvetica-Bold")
            .fontSize(9)
            .fillColor(COLORS.text)
            .text("Authorised Signatory", rightSigX, sigLineY + 4, {
                width: sigLineWidth,
                align: "right",
            });

        doc
            .font("Helvetica-Oblique")
            .fontSize(9)
            .fillColor(COLORS.muted)
            .text("This is a computer-generated invoice.", PAGE_MARGIN, doc.page.height - PAGE_MARGIN - 20, {
                width: pageWidth,
                align: "center",
            });
    }

    private static formatCurrency(val: any): string {
        const num = Number(val);
        if (isNaN(num)) return "Rs. 0.00";
        return `Rs. ${num.toFixed(2)}`;
    }
}
