#include "worker.h"
#include "update.h"

#include <QCoreApplication>
#include <QApplication>
#include <QDebug>
#include <QTimer>
#include <QFileInfo>
#include <QDir>
#include <QDirIterator>
#include <QThread>
#include <QRegularExpression>
#include <QDateTime>
#include <QString>

#include "message_handler.h"
#include "plugins/interfaces.h"
#include "ismas/ismas_client.h"
#include "apism/apism_client.h"

int Worker::read1stLineOfFile(QString fileName) {
    QFile f(fileName);
    if (f.exists()) {
        if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&f);
            in.setCodec("UTF-8");
            while(!in.atEnd()) {
                return in.readLine().toInt();
            }
        }
    }
    return -1;
}

Worker::Worker(hwinf *hw,
               int customerNr,
               int machineNr,
               int zoneNr,
               QString branchName,
               QString workingDirectory,
               bool maintenanceMode,
               bool dryRun,
               QObject *parent,
               char const *serialInterface,
               char const *baudrate)
  : m_workerThread("workerThread")
  , m_apismClient(0, 0, 0, this) // TODO
  , m_customerNr(customerNr)
  , m_customerNrStr(QString("customer_") + QString::number(m_customerNr).rightJustified(3, '0'))
  , m_machineNr(machineNr)
  , m_zoneNr(zoneNr)
  , m_workingDirectory(workingDirectory)
  , m_branchName(branchName)
  , m_customerRepository(QDir::cleanPath(m_workingDirectory + QDir::separator() + m_customerNrStr))
  , m_gc(m_customerNrStr, m_customerRepository, m_workingDirectory, m_branchName, this)
  , m_maintenanceMode(maintenanceMode)
  , m_osVersion(getOsVersion())
  , m_ismasUpdateRequests(ISMAS_UPDATE_REQUESTS)
  , m_waitForNewUpdates(this) {

    QDir::setCurrent(m_workingDirectory);

    qInfo() << "CURRENT TIME ..............." << QDateTime::currentDateTime().toString(Qt::ISODate);
    qInfo() << "OS VERSION ................." << m_osVersion;
    qInfo() << "CUSTOMER_NR ................" << m_customerNr;
    qInfo() << "CUSTOMER_NR_STR ............" << m_customerNrStr;
    qInfo() << "CUSTOMER_REPOSITORY_PATH ..." << QString("https://git.mimbach49.de/GerhardHoffmann/%1.git").arg(m_customerNrStr);
    qInfo() << "CUSTOMER_REPOSITORY ........" << m_customerRepository;
    qInfo() << "MACHINE_NR ................." << m_machineNr;
    qInfo() << "ZONE_NR ...................." << m_zoneNr;
    qInfo() << "BRANCH_NAME ................" << m_branchName;
    qInfo() << "WORKING_DIRECTORY .........." << m_workingDirectory;

    QProcess p;
    p.start("/bin/systemctl", {"restart", "apism"});
    if (!p.waitForStarted(5000) || !p.waitForFinished(5000)) {
        qCritical() << "APISM-RESTART-FAILURE";
        return;
    }

    this->moveToThread(&m_workerThread);
    m_workerThread.start();

    int cnt = 0;
    while (!m_workerThread.isRunning()) {
        if (++cnt > 5) {
            qCritical() << "starting worker thread FAILED";
            return;
        }
        QThread::sleep(1);
    }

    connect(&m_apismClient, SIGNAL(ismasResponseAvailable(QJsonObject)), this,
            SLOT(onIsmasResponseReceived(QJsonObject)));
    connect(this, SIGNAL(summarizeRepositoryStatus()), this,
            SLOT(onSummarizeRepositoryStatus()), Qt::QueuedConnection);
    connect(this, SIGNAL(sendCmdSendVersionToIsmas()), this,
            SLOT(onSendCmdSendVersionToIsmas()), Qt::QueuedConnection);
    connect(this, SIGNAL(summarizeUpload(QStringList)), this,
            SLOT(onSummarizeUpload(QStringList)), Qt::QueuedConnection);
    connect(this, SIGNAL(handleChangedFiles(QStringList)), this,
            SLOT(onHandleChangedFiles(QStringList)), Qt::QueuedConnection);
    connect(this, SIGNAL(finishUpdateProcess(bool)), this,
            SLOT(onFinishUpdateProcess(bool)), Qt::QueuedConnection);
    connect(this, SIGNAL(terminateUpdateProcess()), this,
            SLOT(onTerminateUpdateProcess()), Qt::QueuedConnection);

    QDir customerRepository(m_customerRepository);
    if (!customerRepository.exists()) {
        if (m_gc.gitCloneAndCheckoutBranch()) {
            // do nothing else, not even executing opkg-commands
            onFinishUpdateProcess(false);
        }
    } else {
        m_update = new Update(hw, m_customerRepository, m_customerNrStr,
                              m_branchName, m_workingDirectory,
                              dryRun, parent, serialInterface, baudrate);

        connect(&m_startUpdateProcess, SIGNAL(timeout()), this, SLOT(askIsmasForNewData()), Qt::QueuedConnection);
        m_startUpdateProcess.setSingleShot(true);
        m_startUpdateProcess.start(1000);

        connect(&m_waitForNewUpdates, SIGNAL(timeout()), this, SLOT(askIsmasForNewData()), Qt::QueuedConnection);
        m_waitForNewUpdates.setSingleShot(false);
    }
}

Worker::~Worker() {
    int cnt = 0;
    m_workerThread.quit();
    while (!m_workerThread.isFinished()) {
        if (!m_workerThread.wait(1000)) {
            if (++cnt > 5) {
                qCritical() << "stopping worker thread FAILED";
                return;
            }
        }
    }
    if (m_update) {
        delete m_update;
    }
}

QString Worker::getOsVersion() const {
    QString const cmd = QString("echo -n $(cat /etc/os-release | head -n 1 | cut -d'\"' -f2 | tr -d '\"')");
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

void Worker::onHandleChangedFiles(QStringList changedFiles) {
    qCritical() << QDir::currentPath() << "ON HANDLE CHANGED FILES" << changedFiles;

    QString opkg_commands;
    static const QRegularExpression re("^.*opkg_commands\\s*$");
    static const QRegularExpression comment("^\\s*#.*$");
    int idx = changedFiles.indexOf(re);
    if (idx != -1) {
        m_updateStatus = UPDATE_STATUS::EXEC_OPKG_COMMANDS_REQUEST;
        m_statusDescription = "EXECUTE OPKG COMMANDS";

        opkg_commands = changedFiles.takeAt(idx);

        QFile f(opkg_commands);
        if (f.open(QIODevice::ReadOnly)) {
            QTextStream in(&f);
            while (!in.atEnd()) {
                QString line = in.readLine();
                if (line.indexOf(comment, 0) == -1) {
                    // found opkg command
                    QString opkgCommand = line.trimmed();
                    executeOpkgCommand(opkgCommand);
                }
            }
        }

        // m_updateStatus = UPDATE_STATUS::EXEC_OPKG_COMMANDS_SUCCESS;
        // m_statusDescription = QString("EXECUTE OPKG COMMANDS %1 OK").arg(opkgCommands.join('\n'));

        f.close();
    }

    if (m_update->doUpdate(changedFiles)) { // first update the hardware
                                            // then sync the file-system
        if (QDir(m_customerRepository).exists()) {
            if (QDir::setCurrent(m_customerRepository)) {
                QString const params("--recursive             "
                                     "--progress              "
                                     "--checksum              "
                                     "--exclude=.*            "
                                     "--include=*.bin         "
                                     "--include=*.json        "
                                     "--include=opkg_commands "
                                     "--include=*.ini");
                QStringList cmds;
                cmds << QString("rsync ") + params.simplified() + " etc/ /etc";
                cmds << QString("rsync ") + params.simplified() + " opt/ /opt";

                QString cmd;
                bool error = false;
                foreach (cmd, cmds) {
                    if (!error) {
                        Command c("bash");
                        qInfo() << "EXCUTING CMD..." << cmd;
                        if (c.execute(m_customerRepository, QStringList() << "-c" << cmd)) {
                            qDebug() << c.getCommandResult();
                        } else {
                            qCritical() << "CMD" << cmd << "FAILED";
                            error = true;
                        }
                    }
                }
                if (!error) {
                    onFinishUpdateProcess(true);
                    return;
                }
            }
        }
    }
    onTerminateUpdateProcess();
}

void Worker::onSummarizeUpload(QStringList changedFiles) {
    QDateTime const c = QDateTime::currentDateTime();
    QDate const d = c.date();
    QTime const t = c.time();

    QString uploadHistoryFile = QString("upload_history_%1%2%3T%4%5%6.txt")
            .arg(d.year()).arg(d.month()).arg(d.day())
            .arg(t.hour()).arg(t.minute()).arg(t.second());

    QFile f(uploadHistoryFile);
    if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream out(&f);
        QString fName;
        foreach (fName, changedFiles) {
            QString lastCommit = m_gc.gitLastCommit(fName);
            out << fName << ":" << lastCommit << "\n";
        }
    } else {
        // TODO: error an ISMAS
    }
}

void Worker::onSummarizeRepositoryStatus() {
    // TODO
    QString dir("/opt/app/tools/atbupdate/customer_999");
    QDirIterator it(dir, QStringList() << "*.jpg",
                    QDir::Files, QDirIterator::Subdirectories);
    while (it.hasNext()) {
        qDebug() << it.next();
        if (m_gc.gitIsFileTracked(it.next())) {
            QString lastCommit = m_gc.gitLastCommit(it.next());
        }
    }

    /*
    QString repoStatusHistoryFile = QString("repo_status_history_%1%2%3T%4%5%6.txt")
            .arg(d.year()).arg(d.month()).arg(d.day())
            .arg(t.hour()).arg(t.minute()).arg(t.second());
    if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream out(&f);
        QString fName;
        foreach (fName, changedFiles) {
            QString lastCommit = m_gc.gitLastCommit(fName);
            out << fName << ":" << lastCommit << "\n";
        }
    } else {
        // TODO: error an ISMAS
    }
    */
}

void Worker::executeOpkgCommand(QString opkgCommand) {
    Command c(opkgCommand);
    if (c.execute(m_workingDirectory)) {
        QString const r = c.getCommandResult();
        m_updateStatus = UPDATE_STATUS::EXEC_OPKG_COMMAND_SUCCESS;
        m_statusDescription = QString("EXECUTE OPKG COMMAND %1 OK").arg(opkgCommand);
    } else {
        m_updateStatus = UPDATE_STATUS::EXEC_OPKG_COMMAND_FAILURE;
        m_statusDescription = QString("EXECUTE OPKG COMMAND %1 FAILED").arg(opkgCommand);
        onTerminateUpdateProcess();
        return;
    }
}

// sollte ParameterResponse heissen
void Worker::onIsmasResponseReceived(QJsonObject ismasResponse) {
    if (!ismasResponse.isEmpty()) {
        QStringList const keys = ismasResponse.keys();
        static QRegularExpression re("^REQ_ISMASPARAMETER.*");
        if(keys.indexOf(re) >= 0) {
            m_waitForNewUpdates.stop(); // stop asking ISMAS for updates

            // sanity check: cust_nr and machine_nr of PSA correct ?
            if (keys.contains("Dev_ID", Qt::CaseInsensitive)) {
                QJsonObject const devId = ismasResponse["Dev_ID"].toObject();
                QStringList const keys = devId.keys();
                if (keys.contains("Custom_ID") && keys.contains("Device_ID")) {
                    QJsonValue const c = devId.value("Custom_ID");
                    QJsonValue const m = devId.value("Device_ID");
                    int customerNr = c.toInt(-1);
                    int machineNr = m.toInt(-1);
                    if (customerNr != m_customerNr) {
                        m_updateStatus = UPDATE_STATUS::ISMAS_UPDATE_REQUEST_FAILURE;
                        m_statusDescription
                            = QString("CUSTOMER-NR (%1) != LOCAL CUSTOMER-NR (%2)")
                                .arg(customerNr).arg(m_customerNr);
                        return;
                    }
                    if (machineNr != m_machineNr) {
                        m_statusDescription
                            = QString("MACHINE-NR (%1) != LOCAL MACHINE-NR (%2)")
                                .arg(machineNr).arg(m_machineNr);
                        m_updateStatus = UPDATE_STATUS::ISMAS_UPDATE_REQUEST_FAILURE;
                        return;
                    }
                }
            }
            // TODO: check if zone_nr is correct

            if (keys.contains("Fileupload", Qt::CaseInsensitive)) {
                QJsonObject fileUpload = ismasResponse["Fileupload"].toObject();
                QJsonValue v = fileUpload.value("TRG");
                if (!v.isNull() && !v.isUndefined()) {
                    QString const s = v.toString("");
                    if (s == "WAIT") {
                        m_updateStatus = UPDATE_STATUS::ISMAS_UPDATE_REQUEST_SUCCESS;
                        m_ismasUpdateRequests = ISMAS_UPDATE_REQUESTS;
                        m_statusDescription = "ISMAS UPDATES AVAILABLE";
                        emit m_gc.ismasUpdatesAvailable();
                    }
                }
            } else {
                m_updateStatus = UPDATE_STATUS::ISMAS_UPDATE_REQUEST_FAILURE;
                m_statusDescription = "NO FILEUPLOAD KEY AVAILABLE";
                return;
            }
        }
    } else {
        m_updateStatus = UPDATE_STATUS::ISMAS_UPDATE_REQUEST_FAILURE;
        m_statusDescription = "NO ISMAS RESPONSE AVAILABLE (EMPTY)";
    }
}

void Worker::onFinishUpdateProcess(bool changes) {
    qCritical() << "ON FINISH UPDATE PROCESS. CHANGES=" << changes;
    m_workerThread.quit();
    QApplication::quit();
}


void Worker::onTerminateUpdateProcess() {
    qCritical() << "ON TERMINATE UPDATE PROCESS";
    m_workerThread.quit();
    QApplication::quit();
}

void Worker::onSendCmdSendVersionToIsmas() {

    QString const tariffVersion = "0.0.1";
    QString const tariffProject = "test_project";
    int tariffZone = 1;
    QString const tariffInfo = "test_tariff_info";
    QString const tariffLoadTime = QDateTime::currentDateTime().toString(Qt::ISODateWithMs);
    // QString const linuxVersion = "test_linux_version";
    QString const cpuSerial = "test_cpu_serial";
    QString const deviceControllerVersion = "test_dc_version";
    QString const deviceControllerGitBlob = "test_dc_blob_2a3b4f50";
    QString const deviceControllerGitLastCommit = "test_dc_commit_12345abc";
    QString const raucVersion = "test_rauc_version";
    QString const opkgVersion = "test_opkg_version";
    QString const atbQTVersion = "test_qtbqt_version";
    QString const atbQTGitDescribe = "test_atbqt_git_describe";
    QString const deviceControllerPluginVersion = "test_CAmaster_version";
    QString const ingenicoISelfCCPluginVersion = "test_ingenico_plugin_version";
    QString const mobilisisCalculatePricePluginVersion = "test_mobilisis_plugin_version";
    QString const mobilisisCalculatePriceConfigUiVersion = "test_mobilisis_config_ui_plugin";
    QString const prmCalculatePricePluginVersion = "test_prm_calculate_price_plugin";
    QString const prmCalculatePriceConfigUiPluginVersion = "test_prm_calculate_price_config_ui_plugin";
    QString const tcpZVTPluginVersion = "test_tcp_zvt_plugin";

    QString data = m_ismasClient.updateOfPSASendVersion(tariffVersion,
                                                        tariffProject,
                                                        tariffZone,
                                                        tariffInfo,
                                                        tariffLoadTime,
                                                        m_osVersion,
                                                        cpuSerial,
                                                        deviceControllerVersion,
                                                        deviceControllerGitBlob,
                                                        deviceControllerGitLastCommit,
                                                        raucVersion,
                                                        opkgVersion,
                                                        atbQTVersion,
                                                        atbQTGitDescribe,
                                                        deviceControllerPluginVersion,
                                                        ingenicoISelfCCPluginVersion,
                                                        mobilisisCalculatePricePluginVersion,
                                                        mobilisisCalculatePriceConfigUiVersion,
                                                        prmCalculatePricePluginVersion,
                                                        prmCalculatePriceConfigUiPluginVersion,
                                                        tcpZVTPluginVersion);
    m_apismClient.sendCmdSendVersionToIsmas(data);
}

void Worker::askIsmasForNewData() {
    if (m_maintenanceMode) {
        m_updateStatus = UPDATE_STATUS::ISMAS_EMULATE_DATA_AVAILABLE;
        QString data = m_ismasClient.setUpdatesAvailable();
        m_apismClient.emulateUpdatesAvailable(data);
    }
    m_updateStatus = UPDATE_STATUS::ISMAS_UPDATE_REQUEST_PENDING;
    m_apismClient.requestAvailableIsmasUpdates();

    if (--m_ismasUpdateRequests > 0) {
        // if the timer is already running, it will be stopped and restarted.
        m_waitForNewUpdates.start(10000);
        m_updateStatus = UPDATE_STATUS::ISMAS_UPDATE_REQUEST_PENDING;
    } else {
        m_updateStatus = UPDATE_STATUS::ISMAS_UPDATE_REQUEST_TIMEOUT;
        onTerminateUpdateProcess();
    }
}
