#include "ScreenXmlParser.h"

#include <QRegularExpression>
#include <QMap>
#include <QSet>
#include <QList>
#include <QXmlStreamReader>
#include <QDomDocument>
#include <QFile>
#include <QDateTime>

#include "adb/AdbUtils.h"
#include "android/xml/Node.h"
#include "db/AccountInfo.h"
#include "db/TransactionInfo.h"
#include "time/DateUtils.h"

ScreenXmlParser::ScreenXmlParser(QObject *parent) : QObject(parent) {
}

ScreenXmlParser::~ScreenXmlParser() = default;

static const QRegularExpression boundsRegex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])");
static const QRegularExpression onlyDigitsRegex(R"([^0-9])");
static const QRegularExpression onlyDigitsAndDotRegex(R"([^0-9.])");

static const QRegularExpression rePhone(R"raw((\+7\(?\d{3}\)?[- ]?\d{3}[- ]?\d{2}[- ]?\d{2}))raw");
static const QRegularExpression reFromBank(R"raw(из (.+?) на)raw");
static const QRegularExpression reToBank(R"raw(в (.+?) через)raw");
static const QRegularExpression reSbpId(R"(СБП(?: С2С)? ([A-Z0-9]{30,}))");
static const QRegularExpression reIdAlt(R"(ID перевода (\d+))");
static const QRegularExpression reDate(R"((?:Дата операции|дата) (\d{2}\.\d{2}\.\d{4}(?: \d{2}:\d{2}:\d{2})?))");
static const QRegularExpression reSender(R"(от ([^\(]+))");
static const QRegularExpression reNameAlt(R"(Перевод ([А-Яа-яЁё\s\*]+) в)");

static const QRegularExpression reCardData(R"(цифрами\s+(\d\s\d\s\d\s\d).*?Сумма:\s([\d\s]+)\sруб)");

Node ScreenXmlParser::tryToFindBannerOrDialog(const int width, const int height) {
    // 1. Ищем push banner - он перекрывает весь экран
    Node first = m_nodes[0];
    for (const Node &node: m_nodes) {
        //  Если это однозначно, что пуш-баннер
        if (node.resourceId == "aft-popup-pushNotification-iconClose" && node.className == "android.widget.Button") {
            return node;
        }
        // Если есть кнопка "Позже"
        if (node.text.trimmed() == "Позже" && node.className == "android.widget.Button") {
            return node;
        }
        // Если нашли пустую кнпоку
        if (node.className == "android.widget.Button" && node.clickable && node.text.isEmpty()) {
            // <node class="android.widget.Button" package="ru.rshb.dbo" clickable="true"  bounds="[630,142][691,205]"/>
            if (node.x() > width / 2 && node.y() < width / 2) {
                return node;
            }
        }
    }
    return {};
}

/**
 * Смотрим что есть текс "Мы заботимся о вашей безопасности" и
 * кнопка "Отложить"
 * @return координаты кнопки для закрытия
 */
Node ScreenXmlParser::tryToFindSecurityBanner() {
    Node text;
    Node closeBtn;
    for (const Node &node: m_nodes) {
        if (node.text.trimmed() == "Мы заботимся о вашей безопасности" && node.className == "android.app.Dialog") {
            text = node;
        }
        if (node.text.trimmed() == "Отложить" && node.className == "android.widget.Button") {
            closeBtn = node;
        }
    }

    // Не нужно проверять все, если хотя бы одна координата есть
    if (!text.isEmpty() && !closeBtn.isEmpty()) {
        return closeBtn;
    }
    return {};
}

Node ScreenXmlParser::findTextNode(const QString &text) {
    for (const Node &node: m_nodes) {
        if (node.className == "android.widget.TextView" && node.text.trimmed() == text) {
            return node;
        }
    }
    return {};
}

Node ScreenXmlParser::findTextNode(const QString &text, const int x1, const int y1, const int x2, const int y2) {
    for (const Node &node: m_nodes) {
        if (node.className == "android.widget.TextView" && node.text.trimmed() == text
            && node.x() >= x1 && node.x() <= x2 && node.y() >= y1 && node.y() <= y2) {
            return node;
        }
    }
    return {};
}

Node ScreenXmlParser::findFirstEditText() {
    for (const Node &node: m_nodes) {
        if (node.className == "android.widget.EditText" && node.focusable) {
            return node;
        }
    }
    return {};
}

Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) {
    for (const Node &node: m_nodes) {
        if (contains) {
            if (node.className == "android.widget.Button" && node.text.contains(text)) {
                return node;
            }
        } else {
            if (node.className == "android.widget.Button" && node.text.trimmed() == text) {
                return node;
            }
        }
    }
    return {};
}

bool ScreenXmlParser::isHomeScreen() {
    // Есть заголовок "Счета и карты" и кнопка "Все продукты"
    Node text;
    Node allProductBtn;
    for (const Node &node: m_nodes) {
        if (node.text.trimmed() == "Счета и карты" && node.className == "android.widget.TextView") {
            text = node;
        }
        if (node.text.trimmed() == "Все продукты" && node.className == "android.widget.Button") {
            allProductBtn = node;
        }
    }
    return !text.isEmpty() && !allProductBtn.isEmpty();
}

QList<Node> ScreenXmlParser::findPinCodeNodes(const QString &pinCode) {
    QMap<QString, Node> buttons;
    const QSet<QString> numberSet = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};

    for (const Node &node: m_nodes) {
        const QString &text = node.text.trimmed();
        if (node.className == "android.widget.Button" && numberSet.contains(text)) {
            buttons[text] = node;
        }
    }

    QList<Node> nodes;
    for (int i = 0; i < pinCode.length(); ++i) {
        nodes.append(buttons[QString(pinCode.at(i))]);
    }
    return nodes;
}

QList<Node> ScreenXmlParser::tryToFindPinCode(const QString &pinCode, const QString &title) {
    for (const Node &node: m_nodes) {
        // если там есть такая кнопка значит банер есть, и пин-код не прожать
        if (node.text.trimmed() == "Отложить" && node.className == "android.widget.Button") {
            return {};
        }
    }
    for (const Node &node: m_nodes) {
        // FIXME определение пин-кода лучше сделать
        if (node.text.trimmed() == title) {
            return findPinCodeNodes(pinCode);
        }
    }
    return {};
}

QList<Node> ScreenXmlParser::getLast2DaysTransactions() {
    QList<Node> nodes;
    for (const Node &node: m_nodes) {
        if (node.className == "android.widget.Button" && node.text.contains("рубл")
            && (node.text.contains("Минус") || node.text.contains("Плюс"))) {
            nodes.append(node);
        }
    }
    return nodes;
}

/**
* <node NAF="true" index="1" text="" resource-id=""
        class="android.widget.Button" package="ru.rshb.dbo"
        content-desc="" checkable="false" checked="false"
        clickable="true" enabled="true" focusable="false"
        focused="false" scrollable="false"
        long-clickable="false" password="false"
        selected="false" bounds="[952,159][1039,248]"/>
 */
void ScreenXmlParser::parseAndSaveXml(const QString &xml) {
    QDomDocument doc;
    doc.setContent(xml);
    m_xml = doc.documentElement();
    m_nodes.clear();

    QXmlStreamReader reader(xml);
    while (!reader.atEnd()) {
        reader.readNext();
        if (reader.isStartElement() && reader.name() == "node") {
            Node node = Node();
            if (reader.attributes().hasAttribute("bounds")) {
                QString bounds = reader.attributes().value("bounds").toString();
                if (QRegularExpressionMatch match = boundsRegex.match(bounds); match.hasMatch()) {
                    node.setBounds(
                        match.captured(1).toInt(),
                        match.captured(2).toInt(),
                        match.captured(3).toInt(),
                        match.captured(4).toInt()
                    );
                }
            }
            if (reader.attributes().hasAttribute("text")) {
                node.text = reader.attributes().value("text").toString();
            }

            if (reader.attributes().hasAttribute("class")) {
                node.className = reader.attributes().value("class").toString();
            }

            if (reader.attributes().hasAttribute("clickable")) {
                node.clickable = reader.attributes().value("clickable").toString() == "true";
            }
            if (reader.attributes().hasAttribute("focusable")) {
                node.focusable = reader.attributes().value("focusable").toString() == "true";
            }
            if (reader.attributes().hasAttribute("resource-id")) {
                node.resourceId = reader.attributes().value("resource-id").toString();
            }

            m_nodes.append(node);
        }
    }
}

QString getDayOfWeekAsString(const int day_of_week) {
    switch (day_of_week) {
        case 1: return "понедельник";
        case 2: return "вторник";
        case 3: return "среда";
        case 4: return "четверг";
        case 5: return "пятница";
        case 6: return "суббота";
        case 7: return "воскресенье";
        default: return "";
    }
}

QString getMonthAsString(const int month) {
    switch (month) {
        case 1: return "января";
        case 2: return "февраля";
        case 3: return "марта";
        case 4: return "апреля";
        case 5: return "мая";
        case 6: return "июня";
        case 7: return "июля";
        case 8: return "августа";
        case 9: return "сентября";
        case 10: return "октября";
        case 11: return "ноября";
        case 12: return "декабря";
        default: return "";
    }
}

QDomElement findSubElement(const QDomElement &elem, const QString &index) {
    if (elem.hasChildNodes()) {
        QDomNodeList subInfoList = elem.childNodes();
        for (QDomNode subInfoNode: subInfoList) {
            if (!subInfoNode.isElement()) continue;
            QDomElement subInfo = subInfoNode.toElement();
            if (subInfo.attribute("index") == index) {
                return subInfo;
            }
        }
    }
    return {};
}

QString findSubInfo(const QDomElement &elem, const QString &index) {
    QDomElement subInfo = findSubElement(elem, index);
    if (!subInfo.isNull()) {
        return subInfo.attribute("text");
    }
    return "";
}

bool hasTimeComponent(const QString &datetimeStr) {
    QDateTime dt = QDateTime::fromString(datetimeStr, "dd.MM.yyyy HH:mm:ss");
    if (!dt.isValid()) {
        return false; // Строка не соответствует формату даты и времени
    }
    return dt.time() != QTime(0, 0, 0); // Проверка, что время не равно 00:00:00
}

QString transformBankDateTime(const QString &date) {
    QDateTime dt;
    if (date.contains(",")) {
        // 10.05.2025, 15:03
        dt = QDateTime::fromString(date, "dd.MM.yyyy, HH:mm");
    } else {
        dt = QDateTime::fromString(date, "dd.MM.yyyy HH:mm:ss");
    }
    if (!dt.isValid()) {
        dt = QDateTime::fromString(date, "dd.MM.yyyy HH:mm");
    }
    if (!dt.isValid()) {
        return date; // не распарсили
    }
    return dt.toString("dd.MM.yyyy HH:mm:ss");
}

void parseTransferInfo(const QString &text, TransactionInfo &info) {
    if (const auto m = reSender.match(text); m.hasMatch()) {
        info.name = m.captured(1).trimmed();
    } else if (const auto m1 = reNameAlt.match(text); m1.hasMatch()) {
        info.name = m1.captured(1).trimmed();
    }

    if (const auto m = rePhone.match(text); m.hasMatch()) {
        info.phone = m.captured(1).trimmed().remove(onlyDigitsRegex);
    }

    if (const auto m = reFromBank.match(text); m.hasMatch()) {
        if (const QString bank = m.captured(1).trimmed(); !bank.isEmpty()) {
            info.bankName = bank;
        }
    } else if (const auto m1 = reToBank.match(text); m1.hasMatch()) {
        if (const QString bank = m1.captured(1).trimmed(); !bank.isEmpty()) {
            info.bankName = bank;
        }
    }

    if (const auto m = reSbpId.match(text); m.hasMatch()) {
        info.bankTrExternalId = m.captured(1);
    } else if (const auto m1 = reIdAlt.match(text); m1.hasMatch()) {
        info.bankTrExternalId = m1.captured(1);
    }

    if (const auto m = reDate.match(text); m.hasMatch()) {
        const QString date = m.captured(1);
        // если не пустая и дата у нас с 00:00
        // if (hasTimeComponent(date)) {
        info.bankTime = transformBankDateTime(date);
        // }
    }
}

void traverseNodes(const QDomNode &node) {
    if (node.isElement()) {
        QDomElement elem = node.toElement();

        if (node.isElement()) {
            QDomElement elem = node.toElement();

            if (elem.tagName() == "node" &&
                elem.attribute("class") == "android.widget.Button" &&
                elem.attribute("text") == "Детали операции") {
                // Все что ниже деталей операции
                QDomNodeList detailInfo = elem.parentNode().childNodes();


                for (int j = 0; j < detailInfo.count(); ++j) {
                    QDomNode info = detailInfo.at(j);
                    if (!info.isElement()) continue;
                    QDomElement infoElement = info.toElement();
                    QString index = infoElement.attribute("index");
                    switch (index.toInt()) {
                        case 2:
                            qDebug() << "Дата операции:" << findSubInfo(infoElement, "1");
                            break;
                        case 5:
                            qDebug() << "Название:" << findSubInfo(infoElement, "1");
                            qDebug() << "-------------------------------------";
                        // parseTransfer(findSubInfo(infoElement));
                            break;
                        case 7:
                            qDebug() << "Комиссия:" << findSubInfo(infoElement, "1");
                            break;
                        default:
                            qDebug() << "Прочее:" << findSubInfo(infoElement, "1");
                            break;
                    }

                    /**
                    Node: "2" ""
                    ... Node: "0" "Дата операции"
                    ... Node: "1" "30.04.2025 17:46:29"
                    Node: "5" ""
                    ... Node: "0" "Название"
                    ... Node: "1" "DBO TRANSFER RSHB INTERNET-BANK"
                    Node: "7" ""
                    ... Node: "0" "Комиссия"
                    ... Node: "1" "50 ₽"
                    */

                    // if (sibElem.hasChildNodes()) {
                    //     QDomNodeList sibChildren = sibElem.childNodes();
                    //     for (int k = 0; k < sibChildren.count(); ++k) {
                    //         QDomNode sibChild = sibChildren.at(k);
                    //         if (!sibChild.isElement()) continue;
                    //         QDomElement sibChildElem = sibChild.toElement();
                    //         qDebug() << "... Node:"
                    //                 << sibChildElem.attribute("index")
                    //                 << sibChildElem.attribute("text");
                    //         // << sibChildElem.attribute("class");
                    //
                    //
                    //         if (sibChildElem.hasChildNodes()) {
                    //             QDomNodeList sibChildren2 = sibChildElem.childNodes();
                    //             for (int k = 0; k < sibChildren2.count(); ++k) {
                    //                 QDomNode sibChild2 = sibChildren2.at(k);
                    //                 if (!sibChild2.isElement()) continue;
                    //                 QDomElement sibChildElem2 = sibChild2.toElement();
                    //
                    //                 qDebug() << "...... Node:"
                    //                         << sibChildElem2.attribute("index")
                    //                         << sibChildElem2.attribute("text");
                    //                 // << sibChildElem2.attribute("class");
                    //
                    //                 if (sibChildElem2.hasChildNodes()) {
                    //                     QDomNodeList sibChildren3 = sibChildElem2.childNodes();
                    //                     for (int k = 0; k < sibChildren3.count(); ++k) {
                    //                         QDomNode sibChild3 = sibChildren3.at(k);
                    //                         if (!sibChild3.isElement()) continue;
                    //                         QDomElement sibChildElem3 = sibChild3.toElement();
                    //
                    //                         qDebug() << "......... Node:"
                    //                                 << sibChildElem3.attribute("index")
                    //                                 << sibChildElem3.attribute("text");
                    //                         // << sibChildElem2.attribute("class");
                    //                     }
                    //                 }
                    //             }
                    //         }
                    //     }
                    // }
                }
            }
        }
    }

    // Рекурсивно обходим всех детей
    QDomNode child = node.firstChild();
    while (!child.isNull()) {
        traverseNodes(child);
        child = child.nextSibling();
    }
}

QDomNode findNode(const QDomNode &node, const QString &text) {
    if (node.isElement()) {
        QString nodeText = node.toElement().attribute("text");
        if (nodeText.toLower().startsWith(text)) {
            return node;
        }
    }


    QDomNode child = node.firstChild();
    while (!child.isNull()) {
        QDomNode result = findNode(child, text);
        if (!result.isNull()) {
            return result;
        }
        child = child.nextSibling();
    }
    return {};
}

float extractAmounts(const QString &text) {
    // Разрешаем пробелы внутри чисел, захватываем "рубль", "рубля", "рублей" и т.д.
    // QRegularExpression re(R"((Плюс|Минус)\s+([\d\s]+)\s+рубл\w*\s+(\d+)\s+копе\w*)");
    QRegularExpression re(
        R"((Плюс|Минус)\s+([\d\s]+)\s+рубл\w*\s+(\d+)\s+копе\w*)",
        QRegularExpression::UseUnicodePropertiesOption
    );

    QRegularExpressionMatch match = re.match(text);
    if (match.hasMatch()) {
        QString signStr = match.captured(1); // "Плюс" или "Минус"
        QString rublesStr = match.captured(2).remove(' '); // "1 000" → "1000"
        int rubles = rublesStr.toInt();
        int kopecks = match.captured(3).toInt();

        int totalKopecks = rubles * 100 + kopecks;
        if (signStr == "Минус") {
            totalKopecks *= -1;
        }

        return totalKopecks / 100.0f;
    }
    return 0;
}

QString beforeAmount(const QString &text) {
    int index = text.indexOf(QRegularExpression("Плюс|Минус"));
    if (index != -1) {
        return text.left(index).trimmed();
    }
    return text; // если не найдено — вернуть всю строку
}

double parseAmount(const QString &amountAsString) {
    QString numberString = amountAsString;
    numberString.remove(QRegularExpression(R"([^\dточка])"));
    numberString.replace("точка", ".");
    double amount = numberString.toDouble();

    if (amountAsString.startsWith("-")) {
        amount *= -1;
    }

    return amount;
}

QDomElement findElementWithListView(const QDomElement &parent) {
    QDomElement element = parent.firstChildElement();
    while (!element.isNull()) {
        QDomElement child = element.firstChildElement();
        while (!child.isNull()) {
            if (child.hasAttribute("class") && child.attribute("class") == "android.widget.ListView") {
                return child;
            }
            child = child.nextSiblingElement();
        }

        QDomElement found = findElementWithListView(element);
        if (!found.isNull()) {
            return found;
        }
        element = element.nextSiblingElement();
    }
    return {};
}

QList<TransactionInfo> ScreenXmlParser::parseTodayAndYesterdayReportHistory() {
    QList<TransactionInfo> transactions;

    QDomElement child = findElementWithListView(m_xml);
    QDomNodeList listView = findElementWithListView(m_xml).childNodes();
    qDebug() << listView.count();
    if (!listView.isEmpty()) {
        QString date;
        for (QDomNode node: listView) {
            if (node.hasChildNodes()) {
                QString text;
                if (node.childNodes().count() > 1) {
                    date = node.firstChildElement().attribute("text");
                    text = node.lastChildElement().attribute("text");
                } else if (node.childNodes().count() == 1) {
                    text = node.firstChildElement().attribute("text");
                }

                TransactionInfo transaction;
                if (text.startsWith("Перевод")) {
                    QRegularExpression re(R"([+-] \d{1,3}(?: \d{3})* точка \d{2} Рубль$)");
                    QRegularExpressionMatch match = re.match(text);
                    if (match.hasMatch()) {
                        QString amount = match.captured(0);
                        transaction.amount = parseAmount(amount.trimmed());
                        text.chop(amount.length());
                    }
                    transaction.description = text.trimmed();
                    parseTransferInfo(text, transaction);
                    if (transaction.bankTime.isEmpty()) {
                        transaction.bankTime = transformBankDateTime(date);
                    }
                } else {
                    // карта
                    if (text.contains("без учета комиссии")) {
                        QRegularExpression re(R"( без учета комиссии -\d{1,3}(?: \d{3})* точка \d{2} Рубль$)");
                        QRegularExpressionMatch match = re.match(text);
                        if (match.hasMatch()) {
                            QString fee = match.captured(0);
                            text.chop(fee.length());
                            transaction.fee = parseAmount(fee.replace("без учета комиссии", "").trimmed());
                        }
                    }
                    QRegularExpression re(R"([+-] \d{1,3}(?: \d{3})* точка \d{2} Рубль$)");
                    QRegularExpressionMatch match = re.match(text);
                    if (match.hasMatch()) {
                        QString amount = match.captured(0);
                        transaction.amount = parseAmount(amount.trimmed());
                        text.chop(amount.length());
                    }
                    transaction.description = text.trimmed();
                    if (transaction.bankTime.isEmpty()) {
                        transaction.bankTime = transformBankDateTime(date);
                    }
                }
                transactions.append(transaction);
            }
        }
    } else {
        qDebug() << "ListView is empty";
    }
    return transactions;
}

QList<TransactionInfo> ScreenXmlParser::parseTodayAndYesterdayHistory() {
    QDate today = DateUtils::getMoscowNow().date();
    QDate yesterday = today.addDays(-1);
    QDate beforeYesterday = today.addDays(-2);


    QString todayTitle = QString("сегодня, %1").arg(getDayOfWeekAsString(today.dayOfWeek()));
    QString yesterdayTitle = QString("вчера, %1").arg(getDayOfWeekAsString(yesterday.dayOfWeek()));
    QString beforeYesterdayTitle = QString("%1 %2, %3").arg(
        QString::number(beforeYesterday.day()),
        getMonthAsString(beforeYesterday.month()),
        getDayOfWeekAsString(beforeYesterday.dayOfWeek())
    );

    QDomNode todayNode = findNode(m_xml, todayTitle);
    QDomNode yesterdayNode = findNode(m_xml, yesterdayTitle);

    QDomNode parentNode;
    if (!yesterdayNode.isNull()) {
        parentNode = yesterdayNode.parentNode();
    } else if (!todayNode.isNull()) {
        parentNode = todayNode.parentNode();
    }


    QList<TransactionInfo> transactions;
    if (!parentNode.isNull()) {
        QDate date = {};
        for (QDomNode transactionNode: parentNode.childNodes()) {
            QString transactionShortText = transactionNode.toElement().attribute("text");

            const QString titleLowerCase = transactionShortText.toLower();
            if (titleLowerCase.startsWith(beforeYesterdayTitle)) {
                // только сегодня и вчера парсим, позавчера не трогаем
                break;
            }
            if (titleLowerCase.startsWith(todayTitle)) {
                date = today;
                transactionShortText = transactionShortText.mid(todayTitle.length() + 1);
            }
            if (titleLowerCase.startsWith(yesterdayTitle)) {
                date = yesterday;
                transactionShortText = transactionShortText.mid(yesterdayTitle.length() + 1);
            }

            if (transactionShortText.contains("ЕСПП")) {
                continue;
            }

            float amount = extractAmounts(transactionShortText);
            QString shortDesc = beforeAmount(transactionShortText);

            TransactionInfo info;
            info.amount = amount;
            info.bankTime = date.toString("dd.MM.yyyy");
            info.shortDescription = shortDesc;

            transactions.append(info);
        }
    }
    return transactions;
}

void parse2daysHistory(const QDomNode &node) {
    QDate today = DateUtils::getMoscowNow().date();
    QDate tomorrow = today.addDays(-1);
    QDate after2Days = today.addDays(-2);

    QString todayTitle = QString("сегодня, %1").arg(getDayOfWeekAsString(today.dayOfWeek()));
    QDomNode todayNode = findNode(node, todayTitle);
    if (todayNode.isNull()) {
    } else {
        qDebug() << "todayNode: " << todayNode.toElement().attribute("text");
    }

    QString tomorrowTitle = QString("вчера, %1").arg(getDayOfWeekAsString(tomorrow.dayOfWeek()));
    QDomNode tomorrowNode = findNode(node, tomorrowTitle);
    if (tomorrowNode.isNull()) {
    } else {
        qDebug() << "tomorrowNode: " << tomorrowNode.toElement().attribute("text");
    }

    // 30 апреля, Среда
    QString after2DaysTitle = QString("%1 %2, %3").arg(
        QString::number(after2Days.day()),
        getMonthAsString(after2Days.month()),
        getDayOfWeekAsString(after2Days.dayOfWeek()));
    QDomNode after2DaysNode = findNode(node, after2DaysTitle);
    if (after2DaysNode.isNull()) {
        qDebug() << "No after2DaysNode";
    } else {
        qDebug() << "after2DaysNode: " << after2DaysNode.toElement().attribute("text");
    }

    QDomNode parentNode;
    if (!todayNode.isNull() || !tomorrowNode.isNull() || !after2DaysNode.isNull()) {
        if (!todayNode.isNull()) {
            parentNode = todayNode.parentNode();
        }
        if (!tomorrowNode.isNull()) {
            parentNode = tomorrowNode.parentNode();
        }
        if (!after2DaysNode.isNull()) {
            parentNode = after2DaysNode.parentNode();
        }
    }


    if (!parentNode.isNull()) {
        for (QDomNode child_node: parentNode.childNodes()) {
            QString child_text = child_node.toElement().attribute("text");
            if (child_text.toLower().startsWith(todayTitle)) {
                qDebug() << "=== Today: " << todayTitle;
                qDebug() << "node: " << beforeAmount(child_text.mid(todayTitle.length() + 1));
                qDebug() << "--> sum: " << QString::number(extractAmounts(child_text), 'f', 2);
                // qDebug() << "";
                continue;
            }
            if (child_text.toLower().startsWith(tomorrowTitle)) {
                qDebug() << "=== Yesterday: " << todayTitle;
                qDebug() << "node: " << beforeAmount(child_text.mid(tomorrowTitle.length() + 1));
                qDebug() << "--> sum: " << QString::number(extractAmounts(child_text), 'f', 2);
                // qDebug() << "";
                continue;
            }
            if (child_text.toLower().startsWith(after2DaysTitle)) {
                qDebug() << "=== Before yesterday: " << todayTitle;
                qDebug() << "node: " << beforeAmount(child_text.mid(after2DaysTitle.length() + 1));
                qDebug() << "--> sum: " << QString::number(extractAmounts(child_text), 'f', 2);
                // qDebug() << "";
                continue;
            }

            qDebug() << "node: " << beforeAmount(child_node.toElement().attribute("text"));
            qDebug() << "--> sum: " <<
                    QString::number(extractAmounts(child_node.toElement().attribute("text")), 'f', 2);
            // qDebug() << "";
        }
    }


    // QString d = today.day() > 9 ? QString::number(today.day()) : "0" + QString::number(today.day());
    // QString m = today.month() > 9 ? QString::number(today.month()) : "0" + QString::number(today.month());
    // QString y = QString::number(today.year());
    // QString w = getDayOfWeekAsString(today.dayOfWeek());
    // QString mm = getMonthAsString(today.month());
    // qDebug() << "Date:" << d << m << y << w << mm;


    // QString d2 = after2Days.day() > 9 ? QString::number(after2Days.day()) : "0" + QString::number(after2Days.day());
    // QString m2 = after2Days.month() > 9
    //                  ? QString::number(after2Days.month())
    //                  : "0" + QString::number(after2Days.month());
    // QString y2 = QString::number(after2Days.year());
    // QString w2 = getDayOfWeekAsString(after2Days.dayOfWeek());
    // QString mm2 = getMonthAsString(after2Days.month());
    // qDebug() << "Date:" << d2 << m2 << y2 << w2 << mm2;
}

TransactionInfo parseExpandedInfoHistory(const QDomElement &root) {
    if (root.isElement()) {
        QDomElement elem = root.toElement();
        if (elem.tagName() == "node"
            && elem.attribute("class") == "android.widget.Button"
            && elem.attribute("text") == "Детали операции"
        ) {
            TransactionInfo transaction;
            // Все что ниже деталей операции
            QDomNodeList detailInfo = elem.parentNode().childNodes();

            for (int i = 0; i < detailInfo.count(); ++i) {
                QDomNode info = detailInfo.at(i);
                if (!info.isElement()) continue;

                switch (QDomElement e = info.toElement(); e.attribute("index").toInt()) {
                    case 2:
                        // Дата операции
                        transaction.bankTime = transformBankDateTime(findSubInfo(e, "1"));
                        break;
                    case 5:
                        transaction.description = findSubInfo(e, "1");
                        parseTransferInfo(transaction.description, transaction);
                        break;
                    case 7:
                        // Комиссия
                        transaction.fee = findSubInfo(e, "1").remove(onlyDigitsRegex).toFloat();
                        break;
                    default:
                        // qDebug() << "Прочее:" << findSubInfo(e);
                        break;
                }
            }
            return transaction;
        }
    }

    // Рекурсивно обходим всех детей
    QDomNode child = root.firstChild();
    while (!child.isNull()) {
        if (TransactionInfo info = parseExpandedInfoHistory(child.toElement()); !info.bankTime.isEmpty()) {
            return info;
        }
        child = child.nextSibling();
    }

    return {};
}

TransactionInfo ScreenXmlParser::parseTransactionInfoHistory(int screenWidth, int screenHeight) {
    // 1. Распарсить описание транзакции
    TransactionInfo transaction = parseExpandedInfoHistory(m_xml);
    // 1. Найти статус: Исполнен | Операция исполнена
    transaction.status = TransactionStatus::Wait;
    Node status1 = findTextNode("Исполнен", 0, 0, screenWidth, screenHeight / 2);
    Node status2 = findTextNode("Операция исполнена", 0, 0, screenWidth, screenHeight / 2);
    if (!status1.isEmpty() || !status2.isEmpty()) {
        transaction.status = TransactionStatus::Complete;
    }
    return transaction;
}

TransactionInfo parseExpandedInfo(const QDomElement &root) {
    if (root.isElement()) {
        QDomElement elem = root.toElement();
        if (elem.tagName() == "node"
            && elem.attribute("class") == "android.widget.Button"
            && elem.attribute("text") == "Детали операции"
        ) {
            TransactionInfo transaction;
            // Все что ниже деталей операции
            QDomNodeList detailInfo = elem.parentNode().childNodes();

            for (int i = 0; i < detailInfo.count(); ++i) {
                QDomNode info = detailInfo.at(i);
                if (!info.isElement()) continue;

                QDomElement infoElement = info.toElement();
                QString index = infoElement.attribute("index");
                QDomElement ee;
                switch (index.toInt()) {
                    case 3:
                        transaction.bankTime = transformBankDateTime(findSubInfo(infoElement, "1"));
                        break;
                    case 4:
                        ee = findSubElement(infoElement, "0");
                        ee = findSubElement(ee, "1");
                        transaction.phone = findSubInfo(ee, "0").remove(onlyDigitsRegex);
                        break;
                    case 5:
                        transaction.name = findSubInfo(infoElement, "1");
                        break;
                    case 6:
                        transaction.bankName = findSubInfo(infoElement, "1");
                        break;
                    case 9:
                        transaction.fee = findSubInfo(infoElement, "1").remove(onlyDigitsAndDotRegex).toFloat();
                        break;
                    case 10:
                        ee = findSubElement(infoElement, "1");
                        transaction.bankTrExternalId = findSubInfo(ee, "1");
                        break;
                    default:
                        break;
                }
            }
            return transaction;
        }
    }

    // Рекурсивно обходим всех детей
    QDomNode child = root.firstChild();
    while (!child.isNull()) {
        if (TransactionInfo info = parseExpandedInfo(child.toElement()); !info.bankTime.isEmpty()) {
            return info;
        }
        child = child.nextSibling();
    }

    return {};
}

TransactionInfo ScreenXmlParser::parseTransactionInfo(int screenWidth, int screenHeight) {
    // 1. Распарсить описание транзакции
    TransactionInfo transaction = parseExpandedInfo(m_xml);
    // 1. Найти статус: Исполнен | Операция исполнена
    transaction.status = TransactionStatus::Wait;
    Node status1 = findTextNode("Исполнен", 0, 0, screenWidth, screenHeight / 2);
    Node status2 = findTextNode("Операция исполнена", 0, 0, screenWidth, screenHeight / 2);
    if (!status1.isEmpty() || !status2.isEmpty()) {
        transaction.status = TransactionStatus::Complete;
    }
    return transaction;
}


QList<QPair<AccountInfo, Node> > ScreenXmlParser::parseAccountsInfo() {
    QList<QPair<AccountInfo, Node> > accounts;
    for (const Node &node: m_nodes) {
        const QString text = node.text;

        // Проверяем только нужные строки
        if (!text.contains("Номер карты с последними цифрами")) {
            continue;
        }
        QRegularExpressionMatch match = reCardData.match(text);
        if (!match.hasMatch()) {
            continue;
        }

        AccountInfo info;
        info.lastNumbers = match.captured(1).remove(" ");
        info.amount = match.captured(2).remove(" ").toFloat();
        info.description = text.mid(0, text.indexOf("Номер карты"));
        info.updateTime = DateUtils::getUtcNow();

        accounts.append(qMakePair(info, node));
    }

    return accounts;
}

void ScreenXmlParser::test() {
    // TransactionInfo info2 = parseExpandedInfo(m_xml);
    // qDebug().noquote().nospace() << convertTransactionToString(info2);
    // return;

    QFile file("assets/rshb/all_products.xml");
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qDebug() << "Cannot open file";
    }

    QDomDocument doc;
    if (!doc.setContent(&file)) {
        qDebug() << "Failed to parse XML";
    }
    file.close();

    qDebug() << "Start parsing XML:";

    // Номер карты с последними цифрами
    QDomElement root = doc.documentElement();
    parseAndSaveXml(doc.toString());
    // for (const AccountInfo &info: parseAccountsInfo()) {
    // qDebug().noquote().nospace() << convertAccountToString(info);
    // }
    // TransactionInfo info = parseExpandedInfo(root);
    // qDebug().noquote().nospace() << convertTransactionToString(info);
    // ЕСПП
}
