#include "CommonScript.h"

#include <QJsonArray>
#include <QJsonObject>
#include <QRandomGenerator>
#include <QThread>

#include "adb/AdbUtils.h"
#include "net/NetworkService.h"

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

CommonScript::~CommonScript() = default;

void CommonScript::start() {
    doStart();
    emit finished();
}

Node CommonScript::swipeToButton(
    const QString &deviceId,
    const QString &text, int &counter, const int width, const int height
) {
    for (int i = 0; i < 10; ++i) {
        if (Node button = xmlScreenParser.findButtonNode(text, true); button.x() > 0 && button.y() > 0) {
            return button;
        }
        const int x = width / 2;
        const int offset = height / 4;
        AdbUtils::makeSwipe(deviceId, x, height - offset, x, offset);
        xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
    }
    return {};
}

bool CommonScript::inputPinCode(const QString &deviceId, const QList<Node> &pincode) {
    for (const Node &node: pincode) {
        const bool completePincode = AdbUtils::makeTap(deviceId, node.x(), node.y());
        if (!completePincode) {
            return false;
        }
        QThread::msleep(QRandomGenerator::global()->bounded(100, 901));
    }
    return true;
}

bool CommonScript::runAppAndGoToHomeScreen(
    const QString &deviceId,
    const QString &packageName,
    const QString &pinCode,
    const int width,
    const int height
) {
    // Запустить устройство
    if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) {
        qDebug() << "Process already running, pid: " << pid;
        AdbUtils::getScreenDumpAsXml(deviceId, 1000);
        AdbUtils::tryToKillApplication(deviceId, packageName);
    }
    if (!AdbUtils::tryToStartApplication(deviceId, packageName)) {
        AdbUtils::takeScreenshot(deviceId, "run_app_error");
        qWarning() << "Process has not started";
        return false;
    }

    // Даем минимальное время на запуск
    QThread::msleep(4500);
    int counter = 0;
    int restarted = 0;
    int totalCounter = 0;
    bool findAndInputPincode = false;
    while (true) {
        counter++;
        totalCounter++;

        // 3 раза уже перезапускали приложение, завершаем попытки
        if (restarted > 2) {
            AdbUtils::takeScreenshot(deviceId, "3_times_try_start");
            return false;
        }

        // Если более 30 секунд мы пытаемся перейти на домашнюю, перезапускаем
        if (counter > 30) {
            AdbUtils::tryToKillApplication(deviceId, packageName);
            QThread::msleep(4500);
            ++restarted;
            counter = 0;
        }

        xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));

        if (xmlScreenParser.isHomeScreen()) {
            return true;
        }

        // Проверяем на банеры после пин-кодом
        Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height);
        if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) {
            AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y());
            QThread::msleep(1000);
            continue;
        }

        // Проверяем на банеры перед пин-кодом
        Node closeBannerNode = xmlScreenParser.tryToFindSecurityBanner();
        if (!closeBannerNode.isEmpty()) {
            AdbUtils::makeTap(deviceId, closeBannerNode.x(), closeBannerNode.y());
            QThread::msleep(1000);
            continue;
        }

        // Проверяем, что это экран ввода пин-кода
        if (!findAndInputPincode) {
            if (const QList<Node> pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
                pincode.size() == 4) {
                inputPinCode(deviceId, pincode);
                // После ввода пин-код нужно сделать задержку, пока подгрузка идет, чтобы снова не спарсить
                findAndInputPincode = true;
                QThread::msleep(3000);
                continue;
            }
        }

        QThread::msleep(1000);

        // Если что-то пойдет не по плану...
        if (totalCounter > 100) {
            AdbUtils::takeScreenshot(deviceId, "too_many_attempts");
            return false;
        }
    }
}

bool CommonScript::tryToCloseAllBannersOnHomePage(const QString &deviceId, const int width, const int height) {
    Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height);
    if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) {
        AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y());
        return true;
    }

    // TODO тут все другие банеры попробовать закрыть
    return false;
}

bool CommonScript::goToAllProducts(
    const QString &deviceId,
    const int width,
    const int height
) {
    xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, m_counter));
    if (tryToCloseAllBannersOnHomePage(deviceId, width, height)) {
        QThread::msleep(500);
        xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
    }

    if (xmlScreenParser.isHomeScreen()) {
        // идем на экран "Все продукты"
        if (Node allCardTitle = xmlScreenParser.findTextNode("Счета и карты"); !allCardTitle.isEmpty()) {
            // 1. Если нет кнопки видимой, свайпаем. Кликаем на "Все продукты"
            for (int i = 0; i < 5; ++i) {
                Node allProductsBtn = xmlScreenParser.findButtonNode("Все продукты", false);
                if (allProductsBtn.x() > 0 && allProductsBtn.y() > 0) {
                    AdbUtils::makeTap(deviceId, allProductsBtn.x(), allProductsBtn.y());
                    QThread::msleep(1000);
                    break;
                }
                AdbUtils::makeSwipe(deviceId,
                                    allCardTitle.x(), allCardTitle.y(),
                                    allCardTitle.x(), allCardTitle.y() - 200);
                xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
            }

            for (int i = 0; i < 5; ++i) {
                Node allProductsTitle = xmlScreenParser.findTextNode("Все продукты");
                // Проверяем что вверху заголовок
                if (!allProductsTitle.isEmpty() && allProductsTitle.y() < 300) {
                    return true;
                }

                QThread::msleep(1000);
                xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
            }
        } else {
            // FIXME пишем в БД, что не дошли до домашней страницы, и завершаемся
            // сохраняем скрин приложения
            // дделаем алерт
        }
    } else {
        // FIXME
        qDebug() << "Not home screen...";
    }
    return false;
}

bool CommonScript::findAndTapOnAccount(const QString &deviceId, const QString &accountNumbers) {
    QList<QPair<AccountInfo, Node> > accounts = xmlScreenParser.parseAccountsInfo();
    for (auto &[account, node]: accounts) {
        // FIXME будем считать что числа последние везде уникальные
        if (accountNumbers == account.lastNumbers) {
            qDebug() << "findAndTapOnAccount: " << accountNumbers;
            AdbUtils::makeTap(deviceId, node.x(), node.y());
            QThread::msleep(1000);
            return true;
        }
    }
    return false;
}


void CommonScript::postAccountsData(const QList<QPair<AccountInfo, Node> > &accounts) {
    if (accounts.isEmpty()) {
        return;
    }

    auto *thread = new QThread;
    auto *worker = new NetworkService;

    QJsonArray list;
    for (const QPair<AccountInfo, Node> &pair: accounts) {
        QJsonObject obj;
        AccountInfo account = pair.first;
        obj["appName"] = account.appName;
        obj["lastNumbers"] = account.lastNumbers;
        obj["amount"] = account.amount;
        obj["description"] = account.description;

        list.append(obj);
    }
    // FIXME доставить десктоп ид из настроеек
    worker->setDeviceData("DT88bokcwQ", accounts.first().first.deviceId);
    worker->setPayload(QJsonDocument(list).toJson());
    worker->moveToThread(thread);
    connect(thread, &QThread::started, worker, &NetworkService::postAccountsData);
    connect(worker, &NetworkService::finished, thread, &QThread::quit);
    connect(worker, &NetworkService::finished, worker, &QObject::deleteLater);
    connect(thread, &QThread::finished, thread, &QThread::deleteLater);
    thread->start();
}

void CommonScript::postNewTransactionData(const AccountInfo &account, const TransactionInfo &transaction) {
    auto *thread = new QThread;
    auto *worker = new NetworkService;

    QJsonObject obj;
    obj["id"] = transaction.id;
    obj["amount"] = transaction.amount;
    obj["name"] = transaction.name;
    obj["fee"] = transaction.fee;
    obj["status"] = transactionStatusToString(transaction.status);
    obj["type"] = transactionTypeToString(transaction.type);
    obj["phone"] = transaction.phone;
    obj["description"] = transaction.description;
    obj["updatedDescription"] = transaction.updatedDescription;
    obj["bankName"] = transaction.bankName;
    obj["bankTime"] = transaction.bankTime;
    obj["bankTransactionId"] = transaction.bankTrExternalId;

    // FIXME доставить десктоп ид из настроеек
    worker->setDeviceData("DT88bokcwQ", account.deviceId);
    worker->addExtra("lastNumbers", account.lastNumbers);
    worker->addExtra("transactionId", transaction.id);
    worker->setPayload(QJsonDocument(obj).toJson());
    worker->moveToThread(thread);
    connect(thread, &QThread::started, worker, &NetworkService::insertTransactionData);
    connect(worker, &NetworkService::finished, thread, &QThread::quit);
    connect(worker, &NetworkService::finished, worker, &QObject::deleteLater);
    connect(thread, &QThread::finished, thread, &QThread::deleteLater);
    thread->start();
}

void CommonScript::updateTransactionData(
    const AccountInfo &account,
    const int transactionId,
    const TransactionStatus status,
    const QString &oldDesc,
    const QString &newDesc
) {
    auto *thread = new QThread;
    auto *worker = new NetworkService;

    QJsonObject obj;
    obj["id"] = transactionId;
    obj["status"] = transactionStatusToString(status);
    obj["description"] = newDesc;
    obj["updatedDescription"] = oldDesc;

    // FIXME доставить десктоп ид из настроеек
    worker->setDeviceData("DT88bokcwQ", account.deviceId);
    worker->addExtra("lastNumbers", account.lastNumbers);
    worker->setPayload(QJsonDocument(obj).toJson());
    worker->moveToThread(thread);
    connect(thread, &QThread::started, worker, &NetworkService::updateTransactionData);
    connect(worker, &NetworkService::finished, thread, &QThread::quit);
    connect(worker, &NetworkService::finished, worker, &QObject::deleteLater);
    connect(thread, &QThread::finished, thread, &QThread::deleteLater);
    thread->start();
}

void CommonScript::stop() {
    m_running = false;
}
