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

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const FONTS_DIR = path.join(__dirname, "fonts");
const BASE_URL = (process.env.BASE_URL || "http://localhost:5000").replace(/\/$/, "");

/** Convert a stored filename to its full served URL, same logic as ShopController */
const resolveFileUrl = (filename: string | null | undefined, folder: string): string | null => {
    if (!filename) return null;
    // Already a full URL — return as-is
    if (filename.startsWith("http")) return filename;
    return `${BASE_URL}/${folder}/${filename}`;
};

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

const PAGE_MARGIN = 40;

// Reserved vertical space (in points) kept clear at the bottom of the page so the
// items table's Total row never collides with the footer (QR codes / signatures).
const FOOTER_HEIGHT = 180;

export class RetailSaleInvoicePdf {
    static async generate(sale: any, res: Response, options: { lang: string, printSign: boolean } = { lang: 'en', printSign: false }) { // TEMP: Digital signature disabled — change to true to re-enable
        const doc = new PDFDocument({
            size: "A4",
            margin: PAGE_MARGIN,
            bufferPages: true,
        });

        // Register a custom font only for the vegetable name column when Hindi/Tamil is selected.
        // Everything else stays in Helvetica (English).
        let vegFont = 'Helvetica';
        if (options.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 (options.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 (options.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 (options.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 (options.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(sale?.grandTotal ?? sale?.subTotal ?? 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 {
                let signPath = shop.authorizedSign;
                // if (!signPath.startsWith("http")) {
                //     signPath = path.join(process.cwd(), "uploads", "signatures", shop.authorizedSign);
                // }
                signPath = path.join(process.cwd(), "uploads", "signatures", shop.authorizedSign);
                authSignBuf = await this.fetchImageBuffer(signPath);
            } catch (err) {
                console.error("[RetailSaleInvoicePdf] Failed to fetch authorizedSign:", shop.authorizedSign, err);
            }
        }

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

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

        await PdfHeader.draw(doc, pageWidth, PAGE_MARGIN, "INVOICE");

        this.drawCustomerMeta(doc, sale, pageWidth);
        this.drawItemsTable(doc, sale, pageWidth, options.lang, vegFont);
        this.drawFooter(doc, pageWidth, sale, 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, sale: any, pageWidth: number) {
        const boxY = doc.y;
        const boxHeight = 62;
        const rightColWidth = pageWidth * 0.4;
        const leftColWidth = pageWidth - rightColWidth;
        const boxX = PAGE_MARGIN;

        const date = sale?.invoiceDate
            ? new Date(sale.invoiceDate).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 custName = sale?.walkInName || "Walk-in Customer";
        const custMobile = sale?.walkInMobile || "";

        doc
            .font("Helvetica-Bold")
            .fontSize(10.5)
            .fillColor(COLORS.primary)
            .text(custName, boxX + leftPad, boxY + boxHeight / 2 - (custMobile ? 10 : 5), {
                width: leftWidth,
                lineBreak: false,
                ellipsis: true,
            });

        if (custMobile) {
            doc
                .font("Helvetica")
                .fontSize(8.5)
                .fillColor(COLORS.muted)
                .text(`Ph: ${custMobile}`, boxX + leftPad, doc.y + 3, {
                    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", `${sale?.invoiceNo ?? sale?.id}`);
        inlineRow(boxY + 34, "Date", date);

        doc.y = boxY + boxHeight + 20;
    }

    /**
     * Bill-style items table.
     * Only the vegetable product name changes language (uses tamilName / hindiName
     * from the Vegetable master table). All other headers remain in English.
     */
    private static drawItemsTable(doc: PDFKit.PDFDocument, sale: any, pageWidth: number, lang: string, vegFont: string) {
        const cols = {
            no: { x: PAGE_MARGIN, width: pageWidth * 0.07 },
            name: { x: PAGE_MARGIN + pageWidth * 0.07, width: pageWidth * 0.30 },
            bag: { x: PAGE_MARGIN + pageWidth * 0.37, width: pageWidth * 0.12 },
            kg: { x: PAGE_MARGIN + pageWidth * 0.49, width: pageWidth * 0.14 },
            rate: { x: PAGE_MARGIN + pageWidth * 0.63, width: pageWidth * 0.16 },
            total: { x: PAGE_MARGIN + pageWidth * 0.79, width: pageWidth * 0.21 },
        };
        const colBoundaries = [
            PAGE_MARGIN,
            cols.name.x,
            cols.bag.x,
            cols.kg.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", cols.bag.x, textY, { width: cols.bag.width, align: "center" })
                .text("KGS/PCS", cols.kg.x, textY, { width: cols.kg.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[] = sale?.items ?? [];
        let totalBag = 0;
        let totalKg = 0;
        let totalAmount = 0;

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

            // Pick the localised vegetable name from the master table; fall back to English name.
            const vegetable = item?.retailStock?.purchase?.vegetable;
            const defaultName = vegetable?.name ?? "Item";
            let name = defaultName;
            if (lang === "ta" && vegetable?.tamilName) name = vegetable.tamilName;
            else if (lang === "hi" && vegetable?.hindiName) name = vegetable.hindiName;
            else if (lang === "kn" && vegetable?.kannadaName) name = vegetable.kannadaName;
            else if (lang === "ml" && vegetable?.malayalamName) name = vegetable.malayalamName;
            else if (lang === "te" && vegetable?.teluguName) name = vegetable.teluguName;

            let currentFont = vegFont;
            if (name === defaultName) {
                currentFont = "Helvetica";
            }

            let bagStr = "-";
            let kgStr = "-";

            if (item.saleUnit === "KG" || item.saleUnit === "PIECES" || item.saleUnit === "THAAR") {
                kgStr = `${item.quantity ?? item.bags ?? ""}`;
                totalKg += Number(item?.quantity ?? item?.bags ?? 0);
            } else if (item.saleUnit === "BAG" || item.saleUnit === "BOX") {
                bagStr = `${item.bags ?? item.quantity ?? ""}`;
                totalBag += Number((item?.bags ?? item?.quantity) ?? 0);
            }

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

            drawRowGrid(y, rowHeight);

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

            // Product name — use language-specific font only for this cell
            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(name, cols.name.x + 6, textY, { width: cols.name.width - 10 });
            } catch (err) {
                try {
                    doc.font(currentFont).fontSize(9).fillColor(COLORS.text);
                    doc.text(name, cols.name.x + 6, textY, { width: cols.name.width - 10, features: ['-mark', '-mkmk', '-kern', '-blwm', '-abvm', '-dist', '-curs'] as any });
                } catch (err2) {
                    doc.font("Helvetica").fontSize(9).fillColor(COLORS.text);
                    doc.text(defaultName, cols.name.x + 6, textY, { width: cols.name.width - 10, ellipsis: true });
                }
            }

            // Rest of row — back to Helvetica
            doc
                .font("Helvetica")
                .fontSize(9)
                .fillColor(COLORS.text)
                .text(bagStr, cols.bag.x, textY, { width: cols.bag.width, align: "center" })
                .text(kgStr, cols.kg.x, textY, { width: cols.kg.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 });

        doc
            .font("Helvetica-Bold")
            .fontSize(10)
            .fillColor(COLORS.text)
            .text(totalBag > 0 ? `${totalBag}` : "-", cols.bag.x, totalTextY, {
                width: cols.bag.width,
                align: "center",
            })
            .text(totalKg > 0 ? `${totalKg}` : "-", cols.kg.x, totalTextY, {
                width: cols.kg.width,
                align: "center",
            })
            .text(this.formatCurrency(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,
        sale: any,
        leftQrBuf: Buffer | null,
        rightQrBuf: Buffer | null,
        authSignBuf: Buffer | null
    ) {
        const footerTop = doc.page.height - PAGE_MARGIN - FOOTER_HEIGHT;
        const qrSize = 80;

        // Top separator
        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",
            });

        // ---- Totals block, centered in the gap between the two QR codes ----
        const subtotal = Number(sale?.subTotal ?? 0);
        const discount = Number(sale?.discount ?? 0);
        const grandTotal = Number(sale?.grandTotal ?? subtotal);
        const paymentMode = sale?.paymentMode ?? "CASH";

        const totalsX = leftQrX + qrSize + 14;
        const totalsWidth = rightQrX - totalsX - 14;
        let ty = qrY;

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

        totalsRow("Subtotal:", this.formatCurrency(subtotal), { color: COLORS.muted });

        if (discount > 0) {
            totalsRow("Discount:", `- ${this.formatCurrency(discount)}`, { color: COLORS.accent });
        }

        doc
            .moveTo(totalsX, ty + 1)
            .lineTo(totalsX + totalsWidth, ty + 1)
            .strokeColor(COLORS.primary)
            .lineWidth(0.75)
            .stroke();
        ty += 7;

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

        ty += 4;
        totalsRow("Payment Mode:", paymentMode, { size: 8, color: COLORS.text });

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

        // Party's Signature (left)
        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 (right) — draw sign image above the line if available
        const rightSigX = PAGE_MARGIN + pageWidth - sigLineWidth + 10; // Moved slightly right

        if (authSignBuf) {
            // Place the signature image centred above the sign line
            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",
            });

        // ---- Disclaimer ----
        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)}`;
    }
}