const appConfig = require("../../../config");
const Taxjar = require('taxjar');
const fs = require('fs');
const fsPromises = require('fs').promises;
const csv = require('csv-parser');
const path = require('path');
const { randomUUID } = require('crypto');
const { PDFDocument, StandardFonts, rgb } = require('pdf-lib');
const fontkit = require('fontkit');
const bwipjs = require('bwip-js');

class pdfFillData {
    /**
     * @param {string} [pdfDocumentName='Xfinity'] - Наименование документа
     */
    constructor(pdfDocumentName= 'None') {
        this.pdfDocumentName = pdfDocumentName;
        this.taxClient = null;
        this.tariffsCache = [];
        this.socrCache = [];
        this.templatePath = '';
        this.templateCSVPath = '';
        this.templateSocrCSVPath = '';

        this.pdfDoc = null;
        this.customFonts = [];
        this.pages = [];
        this.pageWidth = [];
        this.pageHeight = [];
    }

    getPdfDocumentName() {
        return this.pdfDocumentName;
    }

    getTariffsCache() {
        return this.tariffsCache;
    }

    getSocrCache() {
        return this.socrCache;
    }

    getTemplatePath() {
        return this.templatePath;
    }

    getTemplateCSVPath() {
        return this.templateCSVPath;
    }

    getTemplateSocrCSVPath() {
        return this.templateSocrCSVPath;
    }

    setTemplatePath(template) {
        this.templatePath = template;
    }

    setTemplateCSVPath(template) {
        this.templateCSVPath = template;
    }

    setTemplateSocrCSVPath(template) {
        this.templateSocrCSVPath = template;
    }

    initTaxClient(taxtoken) {
        this.taxClient = new Taxjar({
            apiKey: taxtoken
        });
    }

    clearTariffsCache() {
        this.tariffsCache = [];
    }

    clearSocrCache() {
        this.socrCache = [];
    }

    addTariffsCache(row) {
        this.tariffsCache.push(row);
    }

    addSocrCache(row) {
        this.socrCache.push(row);
    }

    loadTariffs() {
        this.clearTariffsCache();
        fs.createReadStream(this.getTemplateCSVPath())
            .pipe(csv())
            .on('data', (row) => this.addTariffsCache(row))
            .on('end', () => console.log('Tariffs loaded'));
    }

    loadSocr() {
        this.clearSocrCache();
        fs.createReadStream(this.getTemplateSocrCSVPath())
            .pipe(csv())
            .on('data', (row) => this.addSocrCache(row))
            .on('end', () => console.log('Socr loaded'));
    }

    generateFilename(type) {
        // Генерируем UUID
        const uniqueId = randomUUID();
        // Форматируем имя файла
        const filename = `${type}_${uniqueId}.pdf`;
        return filename;
    }

    getListCustomerFonts() {
        return this.customFonts;
    }

    countCustomerFonts() {
        return this.customFonts.length;
    }

    getCustomerFontByIndex(i) {
        if(this.countCustomerFonts() && this.customFonts[i] != undefined) {
            return this.customFonts[i];
        }

        return null;
    }

    getListPages() {
        return this.pages;
    }

    countPages() {
        return this.pages.length;
    }

    getPageByIndex(i) {
        if(this.countPages() && this.pages[i] != undefined) {
            return this.pages[i];
        }

        return null;
    }

    getPageWidth(index) {
        return this.pageWidth[index];
    }

    getPageHeight(index) {
        return this.pageHeight[index];
    }

    setPageWidth(index,width) {
        this.pageWidth[index] = width;
    }

    setPageHeight(index,height) {
        this.pageHeight[index] = height;
    }

    getCreditCardPaymentDate(billingDate) {
        let date = new Date(billingDate);

        date.setDate(date.getDate() - 7);

        if (date.getDay() === 0) {
            date.setDate(date.getDate() - 1);
        }

        const day = date.getDate().toString().padStart(2, '0');
        const month = date.toLocaleDateString('en-US', { month: 'short' });

        return `${month} ${day}`;
    }

    pixelsToPoints(pixels, dpi = 72) {
        return pixels * 72 / dpi;
    }

    convert72to300_X(x) {

        let k = 300 / 72;

        return x * k;
    }

    convert72to300_Y(y, setFontSize, lineHeightFactor, pageHeight) {

        let k = 300 / 72;

        return ((pageHeight * k) - (y * k)) - (setFontSize * lineHeightFactor);
    }

    async initPdfDoc() {
        const pdfBytes = await fsPromises.readFile(this.getTemplatePath());
        this.pdfDoc = await PDFDocument.load(pdfBytes);
        this.pdfDoc.registerFontkit(fontkit);
    }

    async initBaseFonts() {
        this.customFonts.push( await this.pdfDoc.embedFont(StandardFonts.Helvetica) );
    }

    async initCustomFonts(fontList) {
        for(let ifont in fontList) {
            this.customFonts.push( await this.initCustomFont( fontList[ifont] ) );
        }
    }

    async initCustomFont(font) {
        await fsPromises.access(font, fs.constants.R_OK)
            .catch(err => {
                throw new Error(`Not access to file ${font}: ${err.message}`);
            });
        const fontBytes = await fsPromises.readFile(font);

        return await this.pdfDoc.embedFont(fontBytes);
    }

    async getTaxRateByAddress(address) {
        try {
            // Улучшенная логика парсинга
            const addressParts = address.split(',').map(part => part.trim());

            // Предполагаем, что последняя часть содержит Город, Штат и ZIP
            // Например: "RALEIGH NC 27613" или "PHILADELPHIA, PA, 19104-5530"
            const lastPart = addressParts[addressParts.length - 1];

            // Ищем ZIP-код (либо 5 цифр, либо 5-4)
            const zipCodeMatch = lastPart.match(/\d{5}(-\d{4})?/);
            const zipCode = zipCodeMatch ? zipCodeMatch[0] : null;

            // Ищем двухбуквенный код штата
            const stateMatch = lastPart.match(/\b([A-Z]{2})\b/);
            const state = stateMatch ? stateMatch[1] : null;

            // Город - это то, что осталось в последней части до кода штата
            let city = null;
            if (state) {
                city = lastPart.substring(0, lastPart.indexOf(state)).trim();
            } else if (addressParts.length > 1) {
                // Если штат не найден в последней части, берем предпоследнюю часть адреса как город
                city = addressParts[addressParts.length - 2];
            }

            console.log({
                originalAddress: address,
                extractedCity: city,
                extractedState: state,
                extractedZipCode: zipCode
            });

            if (!zipCode) {
                throw new Error('Incorrect or missing postcode in address');
            }

            // TaxJar достаточно умен, чтобы работать только с 5-значным ZIP,
            // но передача города и штата помогает уточнить ставку.
            const taxData = await this.taxClient.ratesForLocation(zipCode, {
                city: city,
                state: state
            });

            return {
                zipCode,
                combinedRate: taxData.rate.combined_rate, // Обратите внимание: в ответе API v2 это `taxData.rate.combined_rate`
                stateRate: taxData.rate.state_rate,
                cityRate: taxData.rate.city_rate,
                countyRate: taxData.rate.county_rate,
                details: taxData.rate // Возвращаем весь объект `rate`
            };
        } catch (error) {
            // Пробрасываем ошибку дальше, но добавляем контекст
            const errorMessage = `Error while getting tax rate: ${error.detail || error.message}`;
            console.error(errorMessage);
            throw new Error(errorMessage);
        }
    }

    async checkAccessTemplate() {
        await fsPromises.access(this.getTemplatePath(), fs.constants.R_OK)
            .catch(err => {
                throw new Error(`Not access to file ${this.getTemplatePath()}: ${err.message}`);
            });
    }

    async initPages() {
        this.pages = this.pdfDoc.getPages();

        for (let whc in this.pages) {
            let cropBoxL = this.pages[whc].getCropBox();
            console.log('Page: ', whc, ' Crop box:', cropBoxL);
            this.setPageWidth(whc,cropBoxL.width);
            this.setPageHeight(whc,cropBoxL.height);
        }
    }

    async alignTextToRight(ipage, ifont, setFontSize, setRightMargin, setText) {

        if (typeof setFontSize !== 'number' || typeof setRightMargin !== 'number' || typeof setText !== 'string') {
            throw new Error('Invalid input parameters');
        }

        const textWidth = this.getCustomerFontByIndex(ifont).widthOfTextAtSize(setText, setFontSize);
        const pageWidth = this.pages[ipage].getWidth();

        if (isNaN(textWidth) || isNaN(pageWidth)) {
            throw new Error('Text width or page width calculation resulted in NaN');
        }

        const x = pageWidth - textWidth - setRightMargin;

        return parseInt( !isNaN(x) && x >= 0 ? x : 0 );
    }

    // Этот метод должен быть в классе pdfFillData (pdfFillData.js)

    /**
     * Рисует строку со сложным форматированием, используя вложенные теги <style>.
     * Поддерживаемые атрибуты: font, size, bg, color, padding.
     * @param {object} item - Объект из map.json с описанием поля.
     * @param {object} constants - Объект constants из map.json.
     */
    async drawStyledText(item, constants) {
        const page = this.getPageByIndex(item.ipage);
        if (!page) return;

        // --- 1. Получаем базовые параметры из item ---
        const pageHeight = this.getPageHeight(item.ipage);
        const x_ill_start = this._evaluate(item.x_ill, constants);
        const y_ill_start = this._evaluate(item.y_ill, constants);
        // Базовый размер шрифта из item.size, если в теге не указан другой
        const baseSize = this._evaluate(item.size, constants);
        // Базовый шрифт из item.ifont, если в теге не указан другой
        const baseFontIndex = this._evaluate(item.ifont, constants) || 0;
        // Базовый цвет текста (черный по умолчанию)
        const baseColor = rgb(
            this._evaluate(item.cR, constants) || 0,
            this._evaluate(item.cG, constants) || 0,
            this._evaluate(item.cB, constants) || 0
        );
        
        // --- 2. Подставляем значения переменных в шаблон ---
        let populatedText = item.field;
        const variableNames = [...populatedText.matchAll(/\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g)].map(m => m[1]);
        for(const varName of variableNames) {
            if(this[varName] !== undefined && ['style', 'font', 'size', 'bg', 'color', 'padding'].indexOf(varName) === -1) {
                populatedText = populatedText.replace(new RegExp(`\\b${varName}\\b`, 'g'), this[varName]);
            }
        }
        
        // --- 3. Парсинг строки в "токены" (теги и текст) ---
        const regex = /(<\/?style.*?>)|([^<]+)/g;
        const tokens = [...populatedText.matchAll(regex)].map(m => m[0]);

        // --- 4. Отрисовка с использованием стека стилей ---
        let currentX = x_ill_start;
        const styleStack = [{
            fontIndex: baseFontIndex,
            size: baseSize,
            color: baseColor,
            bgColor: null,
            padding: 0
        }];

        for (const token of tokens) {
            if (token.startsWith('<style')) { // Открывающий тег
                const newStyle = { ...styleStack[styleStack.length - 1] }; // Наследуем предыдущий стиль

                const attrRegex = /(\w+)=(['"])(.*?)\2/g;
                let attrMatch;
                while ((attrMatch = attrRegex.exec(token)) !== null) {
                    const [, key, , value] = attrMatch;
                    
                    if (key === 'font') newStyle.fontIndex = parseInt(value, 10);
                    if (key === 'size') newStyle.size = parseFloat(value);
                    if (key === 'padding') newStyle.padding = parseFloat(value);

                    if (key === 'bg' || key === 'color') {
                        const [r_const, g_const, b_const] = value.split(',').map(c => c.trim());
                        const r = this._evaluate(r_const, constants);
                        const g = this._evaluate(g_const, constants);
                        const b = this._evaluate(b_const, constants);
                        if (![r, g, b].some(isNaN)) {
                            const colorObj = rgb(r, g, b);
                            if (key === 'bg') newStyle.bgColor = colorObj;
                            else newStyle.color = colorObj;
                        }
                    }
                }
                styleStack.push(newStyle);

            } else if (token === '</style>') { // Закрывающий тег
                if (styleStack.length > 1) styleStack.pop();

            } else { // Это текстовый контент
                const text = token;
                const currentStyle = styleStack[styleStack.length - 1];
                const font = this.getCustomerFontByIndex(currentStyle.fontIndex);
                const fontSize = currentStyle.size;
                
                if (!font || !text || isNaN(fontSize)) continue;

                const textWidth = font.widthOfTextAtSize(text, fontSize);
                const textHeight = font.heightAtSize(fontSize);
                // Y-координата вычисляется на основе размера шрифта ТЕКУЩЕГО блока
                const y_pdf = pageHeight - y_ill_start - (fontSize * this.baselineCorrectionFactor);
                
                if (currentStyle.bgColor) {
                    const padding = currentStyle.padding || 1;
                    page.drawRectangle({
                        x: currentX - padding,
                        y: y_pdf - (textHeight * 0.25) - padding,
                        width: textWidth + (padding * 2),
                        height: textHeight + (padding * 2),
                        color: currentStyle.bgColor,
                    });
                }
                
                page.drawText(text, {
                    x: currentX,
                    y: y_pdf,
                    font: font,
                    size: fontSize,
                    color: currentStyle.color,
                });
                
                currentX += textWidth;
            }
        }
    }

    /**
     * Универсальный SVG-парсер. Находит текстовые плейсхолдеры в SVG и заменяет их.
     * @param {number} ipage - Индекс PDF-страницы.
     * @param {CheerioAPI} $ - Распарсенный Cheerio объект с содержимым SVG.
     * @param {object} replacements - Карта для замены вида { 'плейсхолдер': 'новое значение' }.
     */
    async parseAndDrawSVG(ipage, $, replacements) {
        const page = this.getPageByIndex(ipage);
        const pageHeight = this.getPageHeight(ipage);
        if (!page) return;

        // Парсинг CSS теперь тоже часть этого универсального метода
        const styleMap = {};
        $('style').each((i, style) => {
            const styleContent = $(style).text();
            const cssRuleRegex = /\.([^\{]+)\{([^\}]+)\}/g;
            let match;
            while ((match = cssRuleRegex.exec(styleContent)) !== null) {
                const className = match[1].trim();
                const rules = match[2].trim();
                styleMap[className] = {};
                rules.split(';').forEach(rule => {
                    if (rule) {
                        const [key, value] = rule.split(':');
                        if (key && value) {
                            styleMap[className][key.trim()] = value.trim().replace(/['"]/g, '');
                        }
                    }
                });
            }
        });

        const textElements = $('text'); // Сначала получаем все элементы

        for (const textElement of textElements) {
            const textEl = $(textElement); // Оборачиваем в cheerio
            const originalText = textEl.text().replace(/\s+/g, ' ').trim();
            if (!originalText) continue; // continue вместо return

            if (replacements.hasOwnProperty(originalText)) {
                const newText = replacements[originalText];
                
                const transformAttr = textEl.attr('transform');
                const matrix = transformAttr ? transformAttr.match(/matrix\(([^)]+)\)/) : null;
                if (!matrix) continue;

                // ... (весь остальной код внутри if остается БЕЗ ИЗМЕНЕНИЙ) ...
                const [scaleX, skewY, skewX, scaleY, x_svg, y_svg] = matrix[1].split(/[ ,]+/).map(parseFloat);
                const styleSource = textEl.find('[class]').first().length ? textEl.find('[class]').first() : textEl;
                const elClasses = styleSource.attr('class') ? styleSource.attr('class').split(' ') : [];
                let effectiveStyle = {};
                elClasses.forEach(cls => {
                    if (styleMap[cls]) {
                        Object.assign(effectiveStyle, styleMap[cls]);
                    }
                });

                const fontFamily = (effectiveStyle['font-family'] || 'Helvetica').replace(/['"]/g, '');
                const fontSizeAttr = effectiveStyle['font-size'] || '10px';
                const baseFontSize = parseFloat(fontSizeAttr.replace('px', ''));
                const effectiveFontSize = baseFontSize * scaleY;
                const ifont = this.fontMap && this.fontMap[fontFamily] !== undefined ? this.fontMap[fontFamily] : 0;
                const font = this.getCustomerFontByIndex(ifont);

                if (!font) continue;

                let x_pdf = x_svg;
                const align = textEl.attr('pge:align');
                
                if (align) {
                    const originalWidth = font.widthOfTextAtSize(originalText, effectiveFontSize);
                    const newWidth = font.widthOfTextAtSize(newText, effectiveFontSize);
                    if (align === 'right') {
                        x_pdf = x_svg + originalWidth - newWidth;
                    } else if (align === 'center') {
                        x_pdf = x_svg + (originalWidth / 2) - (newWidth / 2);
                    }
                }
                
                const y_pdf = pageHeight - y_svg;
                const fillColor = effectiveStyle['fill'] || '#000000';
                let cR=0, cG=0, cB=0;
                if (fillColor.startsWith('#')) {
                    cR = parseInt(fillColor.substring(1, 3), 16) / 255;
                    cG = parseInt(fillColor.substring(3, 5), 16) / 255;
                    cB = parseInt(fillColor.substring(5, 7), 16) / 255;
                }

                // Теперь await здесь полностью валиден
                await pdfFillData.prototype.draw.call(this, ipage, newText, x_pdf, y_pdf, effectiveFontSize, ifont, cR, cG, cB);
            }
        }
    }


    _getFieldValue(item) { // <-- ПРИНИМАЕТ ВЕСЬ ОБЪЕКТ 'item'
        // Проверка, что item и item.field существуют
        if (!item || typeof item.field !== 'string') {
            console.warn('Item in dataMap is missing a valid "field" property:', item);
            return '';
        }

        const fieldKey = item.field;
        const parts = fieldKey.split(',').map(p => p.trim());
        
        const resolvedParts = parts.map(part => {
            if (this[part] !== undefined && this[part] !== null) {
                return this[part];
            }
            return part;
        });
        
        const separator = item.noSpace ? '' : ' ';
        
        // Соединяем все разрешенные части выбранным разделителем
        return resolvedParts.join(separator).trim();
    }

    _evaluate(expression, constants) {
        if (typeof expression === 'number') return expression;
        if (typeof expression !== 'string') return NaN;
        const formula = expression.replace(/[a-zA-Z_][a-zA-Z0-9_]*/g, (match) => {
            return constants[match] !== undefined ? constants[match] : '0';
        });
        try {
            return new Function(`return ${formula}`)();
        } catch (e) {
            return NaN;
        }
    }

    async generateDataMatrix(textToEncode, bcid, ipage, x_DM, y_DM, s_DMX, s_DMY, scale, rotate) {

        const optimizedText = textToEncode.padStart(30, ' ');//.replace(/\s/g, '');
        console.log('Optimized text:', optimizedText, 'Length:', optimizedText.length);
        console.log('Chars:', optimizedText.split('').map(c => c.charCodeAt(0)));
        const scaleM = scale;

        let pngBuffer = await new Promise((resolve, reject) => {
            bwipjs.toBuffer({
                bcid: bcid, // datamatrixrectangular, datamatrix
                text: optimizedText,
                rotate: rotate,
                scaleX: scaleM,
                scaleY: scaleM,
                width: s_DMX,
                height: s_DMY,
                includetext: false,
                padding: 0,
                options: "rectwidth=12 rectheight=26"
            }, (err, png) => {
                if (err) reject(err);
                else resolve(png);
            });
        });

        const pngImage_DM = await this.pdfDoc.embedPng(pngBuffer);

        this.pages[ipage].drawImage(pngImage_DM, {
            x: x_DM,
            y: y_DM,
            width: s_DMX*scaleM,
            height: s_DMY*scaleM
        });
    }

    async draw(ipage, field, x, y, size, ifont, cR, cG, cB, lineHeight, encoding) {

        const options ={
            x: x,
            y: y,
            size: size,
            font: this.getCustomerFontByIndex(ifont),
            color: rgb(cR, cG, cB),
            encoding: encoding
        }

        // Если lineHeight передан и является числом, добавляем его в опции
        if (typeof lineHeight === 'number' && lineHeight > 0) {
            options.lineHeight = lineHeight;
        }

        this.pages[ipage].drawText(`${field || ''}`, options);
    }

    async drawTextWithBackground(ipage, text, x, y, size, ifont, textColor, bgColor, padding = 1) {
        const page = this.pages[ipage];
        const font = this.getCustomerFontByIndex(ifont);
        if (!page || !font) return;

        // 1. Вычисляем размеры текста
        const textWidth = font.widthOfTextAtSize(text, size);
        const textHeight = font.heightAtSize(size);

        // 2. Рисуем фоновый прямоугольник
        page.drawRectangle({
            x: x - padding, // Начинаем чуть левее текста
            y: y - (textHeight * 0.25) - padding, // Y-координата для прямоугольника.
                                                  // Корректируем, чтобы фон был и над, и под базовой линией.
            width: textWidth + (padding * 2),
            height: textHeight + (padding * 2),
            color: bgColor,
        });

        // 3. Рисуем текст поверх прямоугольника
        page.drawText(text, {
            x: x,
            y: y,
            font: font,
            size: size,
            color: textColor,
        });
    }

    async drawline(ipage, lineWidth, x1, y1, x2, y2, cR, cG, cB) {
        this.pages[ipage].drawLine({
            start: { x: x1, y: y1 },
            end: { x: x2, y: y2 },
            thickness: lineWidth,
            color: rgb(cR, cG, cB),
        });
    }

// В файле pdfFillData.js

// ... (другие методы) ...

    async saveData(host, outputDir, pdfType) {
        const filename = this.generateFilename(pdfType); // generateFilename уже есть
        const outputPath = path.join(outputDir, filename);

        await fsPromises.mkdir(outputDir, { recursive: true })
            .catch(err => {
                if (err.code !== 'EEXIST') throw err;
            });

        const pdfBytesModified = await this.pdfDoc.save();
        await fsPromises.writeFile(outputPath, pdfBytesModified);

        // Возвращаем URL (если нужен где-то еще), локальный путь и имя файла
        return {
            'PDF_URL': outputPath.replace('/srv/pdfgen/public', `https://${host || ''}`),
            'localPath': outputPath, // <--- Важно для отправки файла
            'filename': filename     // <--- Важно для Content-Disposition
        };
    }

// ... (остальной код класса) ...
}

module.exports = pdfFillData;