#include "update_algorithm_thread.h"
#include <unistd.h>
#include "runprocess.h"
#include <QByteArray>
#include <QCryptographicHash>
#include <QDataStream>
#include <QDateTime>
#include <QDebug>
#include <QProcess>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QFileInfoList>
#include <QIODevice>
#include <QInputDialog>
#include <QMessageBox>
#include <QProcess>
#include <QSettings>
#include <QStorageInfo>
#include <QString>
#include <QStringList>
#include <QSysInfo>
#include <QTextStream>
#include <QApplication>
#include <QWidget>
#include <cstdint>
#include <dirent.h>
#include <fstream>
#include <iostream>
#include <regex>
#include <vector>
#include "messager.h"

update_algorithm::update_algorithm(Messager *messager, QObject *parent) : TestThread2(messager, parent)
                                                                            , _messager(messager)
{

}

update_algorithm::~update_algorithm()
{

}

void update_algorithm::dialogueNotification(bool actionResult)
{
    confirmation = actionResult;
}


void update_algorithm::init()
{

}


void update_algorithm::decryptFile(std::string inputFile, std::string outputFile, std::string key)
{
    std::ifstream fin(inputFile, std::ios::binary);
        if (!fin.is_open()) {
            // Обработка ошибки открытия входного файла
            throw std::runtime_error("Unable to open input file: " + inputFile);
        }
    
        std::ofstream fout(outputFile, std::ios::binary);
        if (!fout.is_open()) {
            // Обработка ошибки открытия выходного файла
            fin.close();
            throw std::runtime_error("Unable to open output file: " + outputFile);
        }
    char ch;
    int i = 0;

    while (fin.get(ch))
    {
        ch ^= key[i % key.length()];
        fout << ch;
        i++;
    }

    fin.close();
    fout.close();
}

void update_algorithm::decryptFilesInFolder(std::string folderPath, std::string key2)
{
    DIR *dir;
    struct dirent *ent;
    std::regex fileFilter(".*\\.txt$"); // Фильтр для выбора файлов с расширением .txt

    if ((dir = opendir(folderPath.c_str())) != NULL)
    {
        while ((ent = readdir(dir)) != NULL)
        {
            std::string fileName = std::string(ent->d_name);
            if (std::regex_match(fileName, fileFilter))
            {
                // Получаем имя файла без пути и расширения
                std::string baseFileName = fileName.substr(0, fileName.find_last_of("."));
                // Удаляем строку "Kript" из имени файла
                size_t found = baseFileName.find("Kript");
                if (found != std::string::npos)
                {
                    baseFileName.replace(found, 5, "");
                }
                // Создаем путь к входному и выходному файлам
                std::string inputFilePath = folderPath + "/" + fileName;
                QString inputFilePath_del = QString::fromUtf8(inputFilePath.c_str());
                std::string outputFilePath = folderPath + "/" + baseFileName + ".txt";
                decryptFile(inputFilePath, outputFilePath, key2);
                QFile::remove(inputFilePath_del);
            }
        }
        closedir(dir);
    }
    else
    {
        throw std::runtime_error("Unable to open directory " + folderPath);
    }
}

unsigned int update_algorithm::CRC32_function(unsigned char *buf, unsigned long len)
{
    unsigned long crc_table[256];
    unsigned long crc;
    for (int i = 0; i < 256; i++)
    {
        crc = i;
        for (int j = 0; j < 8; j++)
        crc = crc & 1 ? (crc >> 1) ^ 0x04C11DB7 : crc >> 1;
        crc_table[i] = crc;
    };
    crc = 0xFFFFFFFFUL;
    while (len--)
      crc = crc_table[(crc ^ *buf++) & 0xFF] ^ (crc >> 8);
    return crc ^ 0xFFFFFFFFUL;
}
//
std::vector<unsigned int> update_algorithm::calculateCRC32(const QString &dirPath)
{
    std::vector<unsigned int> myVector; // создание пустого вектора
    QDir dir_prod(dirPath);
    QFileInfoList files_list = dir_prod.entryInfoList();
    for(int i = 0; i < files_list.size(); i++)
    {
        if (files_list.at(i).suffix() == "ini" || files_list.at(i).fileName() == "." || files_list.at(i).fileName() == "..")
        {
            continue;
        }
        else
        {
            QString file_path = files_list.at(i).filePath();
            QFile file(file_path);
            //qDebug() << files_list.at(i).fileName() << file.exists();
            if (file.open(QIODevice::ReadOnly))
            {
            // QByteArray data = file.readAll();
                QDataStream stream(&file);
                stream.setByteOrder(QDataStream::BigEndian);
                QByteArray byteArray;
                char buffer[1024];
                int bytesRead;
                do
                {
                    bytesRead = stream.readRawData(buffer, sizeof(buffer));
                    byteArray.append(buffer, bytesRead);
                } while (bytesRead > 0);

                file.close();
                unsigned int crc32 = CRC32_function(reinterpret_cast<unsigned char *>(byteArray.data()), byteArray.size());
                qDebug() << files_list.at(i).fileName() << crc32;
                // qDebug() << reinterpret_cast<unsigned char *>(data.data()) <<
                // data.data();
                myVector.push_back(crc32);
            }
        }
    }
    return myVector;
}
// возвращает true, если завершение без ошибок, при этом confirmation станет true, если пользователь согласился на обновление
bool update_algorithm::proc()
{
    username = QString(qgetenv("USER")); // Запись текущего пользователя
    QString kernelType = QSysInfo::kernelType();
    QString mountPoint;
    QProcess* unzip_process;
    QString destPath_extraction;

    // Определение точки монтирования в зависимости от типа ядра
    if (kernelType == "linux")
    {
        mountPoint = "/media";
        qDebug() << mountPoint;                

        QDirIterator it(mountPoint, QStringList() << "DevUpdate", QDir::NoFilter, QDirIterator::Subdirectories);
        while (it.hasNext())
        {
            QString dir_DevUpdate = it.next();
            qDebug() << dir_DevUpdate;            

            // Проверка наличия файла updCurr.ini
            QStringList dir_DevUpdate_entries = QDir(dir_DevUpdate).entryList();
            if(dir_DevUpdate_entries.contains("updCurr.ini"))
            {
                _messager->activateMessage(InfoType, "Файл updCurr.ini найден!", dir_DevUpdate.append("/updCurr.ini"), "");

                QDateTime currentDateTime = QDateTime::currentDateTime();
                QString formattedDateTime = currentDateTime.toString("yyyy_MM_dd_hh_mm_ss");
                dir_pathProd = "/home/" + username + "/Prod/Data/Upd/Upd_" + formattedDateTime;
                QDir dir_prod(dir_pathProd);

                // Создание папки
                if (dir_prod.mkpath(dir_pathProd))
                {
                    _messager->activateMessage(InfoType, "Папка была создана", dir_pathProd, "");
                    dir_DevUpdate = dir_DevUpdate.remove("/updCurr.ini");
                    int copied = 0;

                    for (int i = 0; i < dir_DevUpdate_entries.size(); i++)
                    {
                        QString fileName = dir_DevUpdate_entries.at(i);
                        if (fileName == "." || fileName == "..")
                        {
                            continue; // Пропуск специальных файлов
                        }
                        else
                        {
                            QString sourcePath = dir_DevUpdate  + "/" + fileName;
                            QFile file_source(sourcePath);
                            QString destPath = dir_pathProd + "/" + fileName;
                            qDebug() << destPath;

                            if(file_source.exists())
                            {
                                if(!QFile::exists(destPath))
                                {
                                    if (file_source.copy(destPath))
                                    {
                                        if (QFile::exists(destPath))
                                        {
                                            copied++;
                                            qDebug() << "Файл" << sourcePath << "успешно скопирован в" << destPath;
                                        }
                                        else
                                        {
                                            qDebug() << "Ошибка при копировании файла" << sourcePath;
                                        }
                                    }
                                }
                            }
                            else
                            {
                                return false;
                            }
                        }
                    }

                    if(copied == (dir_DevUpdate_entries.size() - 2))
                    {
                        _messager->activateMessage(InfoType, tr("Все файлы скопированы в "), dir_pathProd, "");
                    }
                    else
                    {
                        _messager->activateMessage(InfoType, tr("Не все файлы скопированы в "), dir_pathProd, "");
                        return false;
                    }

                    // Дешифровка файла updCurr.ini
                    QString inputFile = dir_DevUpdate + "/updCurr.ini";
                    QString outputFile = dir_pathProd + "/updCurr.ini";
                    std::string key = "PetroEva";
                    decryptFile(inputFile.toStdString(), outputFile.toStdString(), key);

                    // Открытие decrypted updCurr.ini для чтения
                    QSettings settings(outputFile, QSettings::IniFormat);

                    // Получение списка секций
                    QStringList sections = settings.childGroups();
                    qDebug() << "sections ini file" << sections;

                    // Итерация по секциям
                    foreach (const QString &section, sections)
                    {
                        if (!section.startsWith("file")) continue;

                        QString id = settings.value(section + "/id").toString();
                        QString filename = settings.value(section + "/filename").toString();
                        QString crc32Decrypt = settings.value(section + "/crc32Decrypt").toString();
                        QString crc32Encrypt = settings.value(section + "/crc32Encrypt").toString();
                        QString destdir = settings.value(section + "/destdir").toString();

                        // Проверка значений и добавление в соответствующие структуры
                        if (crc32Encrypt.isEmpty() && crc32Decrypt.isEmpty())
                        {
                            QString message = QString(tr("В секции") + " %1 " + tr("отсутствуют значения crc32Encrypt и crc32Decrypt")).arg(section);
                            _messager->activateMessage(ErrorType, message, "", "");
                            return false;
                        }
                        else
                        {
                            // Конвертация строковых значений в числовые (учитывая шестнадцатеричный формат)
                            bool ok;
                            unsigned int crcDef = crc32Decrypt.toUInt(&ok, 16);
                            if(!ok){
                                _messager->activateMessage(ErrorType, tr("Некорректный формат crc32Decrypt в секции ") + section, "", "");
                                return false;
                            }
                            unsigned int crcEnc = crc32Encrypt.toUInt(&ok, 16);
                            if(!ok){
                                _messager->activateMessage(ErrorType, tr("Некорректный формат crc32Encrypt в секции ") + section, "", "");
                                return false;
                            }

                            crc32Decrypt_from_updCurr.push_back(crcDef);
                            crc32Encrypt_from_updCurr.push_back(crcEnc);

                            // Дополнительная обработка, например, сохранение информации о файлах
                            // Можно использовать структуру или класс для хранения данных о файле
                            // Например:
                            /*
                            FileInfo fi;
                            fi.id = id.toInt();
                            fi.filename = filename;
                            fi.crc32Decrypt = crcDef;
                            fi.crc32Encrypt = crcEnc;
                            fi.destdir = destdir;
                            filesToUpdate.push_back(fi);
                            */
                        }
                    }

                    qDebug() << "crc32Encrypt_from_updCurr" << crc32Encrypt_from_updCurr;
                    qDebug() << "CRC32_Encrypt_from_Prod" << CRC32_Encrypt_from_Prod;

                    if(crc32Encrypt_from_updCurr != CRC32_Encrypt_from_Prod)
                    {
                         _messager->activateMessage(ErrorType, tr("Контрольные суммы"), tr("зашифрованных файлов различаются"), "");
                         return false;
                    }
                    else
                    {
                        _messager->activateMessage(InfoType, tr("Все контрольные суммы"), tr("зашифрованных файлов совпадают"), "");
                    }

                    // Дешифровка всех файлов
                    decryptFilesInFolder(dir_pathProd.toStdString(), key);
                    CRC32_Decrypt_from_Prod = calculateCRC32(dir_pathProd);
                    qDebug() << "crc32Decrypt_from_updCurr" << crc32Decrypt_from_updCurr;
                    qDebug() << "CRC32_Decrypt_from_Prod" << CRC32_Decrypt_from_Prod;
                    if(crc32Decrypt_from_updCurr != CRC32_Decrypt_from_Prod)
                    {
                       _messager->activateMessage(ErrorType, tr("Контрольные суммы"), tr("расшифрованных файлов различаются"), "");
                       return false;
                    }
                    else
                    {
                        _messager->activateMessage(InfoType, tr("Все контрольные суммы"), tr("расшифрованных файлов совпадают"), "");
                    }

                    // Открытие диалогового окна для подтверждения обновления
                    _messager->activateDialogue(tr("Обновить ПО, продолжить") + "?", "", "", this);
                    if(confirmation == false)
                    {
                        return false;
                    }

                    // Получение версии ПО из обновления
                    QString new_soft_version = settings.value("common/version").toString();
                    qDebug() << "Новое значение ключа version:" << new_soft_version;

                    // Получение старой версии ПО
                    QSettings settingsold("/OldVersion/updCurr.ini", QSettings::IniFormat);
                    QString old_soft_version = settingsold.value("common/version").toString();
                    qDebug() << "Старое значение ключа version:" << old_soft_version;

                    // Сравнение версий ПО
                    if (new_soft_version == old_soft_version)
                    {
                        _messager->activateMessage(InfoType, tr("У вас уже установлена"), tr("самая новая версия приложения") + "!", new_soft_version);
                    }
                    else if(new_soft_version < old_soft_version)
                    {
                        _messager->activateDialogue(tr("Хотите перейти на старую версию") + "?", "", "", this);
                        _messager->activateMessage(InfoType, tr("Текущая версия") + ": " + old_soft_version, tr("Новая версия") + ": " + new_soft_version, "");
                        copy();
                    }
                    else if(new_soft_version > old_soft_version)
                    {                         
                        _messager->activateMessage(InfoType, tr("Ваша версия приложения устарела") + "!", tr("Текущая версия") + ": " + old_soft_version, "Новая версия: " + new_soft_version);
                        copy();
                    }
                }
                else
                {
                    _messager->activateMessage(ErrorType, tr("Папка"), dir_pathProd, tr("не была создана"));
                    return false;
                }
            }
            else
            {                
                _messager->activateMessage(ErrorType, "", tr("Файл updCurr.ini не найден") + "!", "");
                return false;
            }
        }
    }
    else
    {
        // Неизвестный тип ядра
        qDebug() << "Неизвестный тип ядра" << kernelType;
        return false;
    }
    return false;
}

void update_algorithm::copy()
{
    QFile file("copy.sh");

    if (file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        QTextStream stream(&file);

        // Записываем текст в файл
        stream << "#!/bin/bash" << Qt::endl;
        stream << "" << Qt::endl;
        stream << "cd /home/petroanalytics/Desktop/build-prod-Desktop_Qt_5_14_2_GCC_64bit-Debug" << Qt::endl;
        stream << "pkill -f prod" << Qt::endl;
        stream << "sleep 5" << Qt::endl;

        // Создание директорий для каждого файла
        stream << "mkdir -p \"/home/" + username + "/Prod/Soft\"" << Qt::endl;
        stream << "mkdir -p \"/home/" + username + "/Prod/Dir\"" << Qt::endl;
        stream << "mkdir -p \"/home/" + username + "/Prod/Crypt\"" << Qt::endl;
        stream << "cp \"/home/" + username + "/reserve/id.txt\" \"/Prod/Soft/id.txt\"" << Qt::endl;
        stream << "cp \"/home/" + username + "/reserve/filename.txt\" \"/Prod/Soft/filename.txt\"" << Qt::endl;
        stream << "cp \"/home/" + username + "/reserve/destdir.txt\" \"/Prod/Dir/destdir.txt\"" << Qt::endl;
        stream << "cp \"/home/" + username + "/reserve/crc32DEncrypt.txt\" \"/Prod/Crypt\"" << Qt::endl;
        stream << "cp \"/home/" + username + "/reserve/crc32Decrypt.txt\" \"/Prod/Crypt\"" << Qt::endl;
        stream << "sleep 5" << Qt::endl;
        stream << "./prod 123" << Qt::endl;
        stream << "exit 0" << Qt::endl;
        //stream << "" << Qt::endl;

        //qDebug() << "Скрипт успешно создан и записан." << file.fileName();
    }
    else
    {
        qDebug() << "Ошибка при записи скрипта" << file.fileName();
    }

    file.close();

    // Установка прав доступа для файла
    file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup | QFile::ReadOther);

    QString upd_Path = "/home/" + username + "/Prod/Data/Upd/";

    QDir dir_upd(upd_Path);

    //qDebug() << upd_Path;

    QString upd_path_withDate;
    QDateTime upd_creationTime;

    QFileInfoList upd_dirs = dir_upd.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Time | QDir::Reversed);
    if (!upd_dirs.isEmpty())
    {
        upd_creationTime = upd_dirs.last().birthTime();
        upd_path_withDate = upd_dirs.last().absoluteFilePath();
        //qDebug() << upd_path_withDate;

        QDir dir_upd_withDate(upd_path_withDate);
    }

    // Проверяем, существует ли папка
    QDir dirReserve("/home/" + username + "/reserve");
    if (dirReserve.exists())
    {
        _messager->activateMessage(InfoType, tr("Папка") + " /home/" + username + "/reserve", tr("уже существует") + "!", "");
    }
    else
    {
        // Создаем папку
        if (dirReserve.mkpath("/home/" + username + "/reserve"))
        {
            _messager->activateMessage(InfoType, tr("Папка") + " /home/" + username + "/reserve", tr("создана") + "!", "");
        }
        else
        {
            _messager->activateMessage(ErrorType, tr("Папка") + " /home/" + username + "/reserve", tr("не была создана") + "!", "");
        }
    }

    QList<QList<QString>> files;
    files.append({upd_path_withDate +"/id.txt", "/home/" + username + "/reserve/id.txt"});
    files.append({upd_path_withDate + "/filename.txt", "/home/" + username + "/reserve/filename.txt"});
    files.append({upd_path_withDate + "/destdir.txt", "/home/" + username + "/reserve/destdir.txt"});
    files.append({upd_path_withDate + "/crc32DEncrypt.txt", "/home/" + username + "/crc32DEncrypt.txt"});
    files.append({upd_path_withDate + "/crc32Decrypt.txt", "/home/" + username + "/crc32Decrypt.txt"});

    QStringList args;
    // Обработка каждого файла в списке
    for (auto file : files)
    {
        QString sourcePath = file[0];
        QString destPath1 = file[1];
        // Если файл уже существует в пути назначения, удаляем его
        if (QFile::exists(destPath1))
        {
            QFile::remove(destPath1);
        }
        // Копирование файла в первый путь назначения
        if (QFile::copy(sourcePath, destPath1))
        {
            qDebug() << "Файл" << QFileInfo(sourcePath).fileName()
                     << "успешно скопирован";
            args << QFileInfo(sourcePath).fileName();
        }
        else
        {
            qDebug() << "Ошибка при копировании файла"
                     << QFileInfo(sourcePath).fileName() << sourcePath;
        }
    }
    //qDebug() << args;
    // Копирование файла в папку назначения
    QString destinationFilePath = upd_path_withDate + "/copy.sh";
    if (QFile::copy("copy.sh", destinationFilePath))
    {
        qDebug() << "Скрипт успешно создан, записан и скопирован в папку" << upd_path_withDate;
    }

    // Удаление файла из папки с исходными кодами
    if (QFile::remove("copy.sh"))
    {
        qDebug() << "Скрипт успешно удален.";
    }

    QString scriptPath; // путь к вашему командному файлу
    QStringList filters;
    filters << "copy.sh";
    QDirIterator it(upd_path_withDate, filters, QDir::Files, QDirIterator::NoIteratorFlags);
    while (it.hasNext())
    {
        scriptPath = it.next();
    }

    QProcess *process;
    QStringList arguments;
    arguments << scriptPath;
    process = new QProcess(nullptr);
    process->startDetached("sh", arguments);
    process->waitForStarted();
    sleep(2);
}

void encryptFile(std::string inputFile, std::string outputFile, std::string key)
{
    // Реализация шифрования, аналогично decryptFile
    decryptFile(inputFile, outputFile, key); // Так как XOR симметричен
}

//Создание и Шифрование .ini Файла:
void createAndEncryptIni(const QString &outputEncryptedPath, const QString &outputDecryptedPath, const std::string &key)
{
    // Создание структуры .ini
    QSettings settings(outputDecryptedPath, QSettings::IniFormat);
    settings.beginGroup("common");
    settings.setValue("allexchange", 1);
    settings.setValue("saveoldversion", 1);
    settings.setValue("restartOS", 1);
    settings.setValue("pause_s", 5);
    settings.setValue("filecount", 5);
    settings.setValue("version", "2023010901");
    settings.endGroup();

    settings.beginGroup("file1");
    settings.setValue("id", 1);
    settings.setValue("filename", "Prod1");
    settings.setValue("crc32Decrypt", "10A4B60C");
    settings.setValue("crc32Encrypt", "D15BF03");
    settings.setValue("destdir", "/Prod/Soft1");
    settings.endGroup();

    // Дешифровка (в данном случае шифрование)
    encryptFile(outputDecryptedPath.toStdString(), outputEncryptedPath.toStdString(), key);
}

//Валидация Структуры .ini:
bool validateIniStructure(const QString &iniPath)
{
    QSettings settings(iniPath, QSettings::IniFormat);

    // Проверка секции [common]
    settings.beginGroup("common");
    bool hasCommon = settings.contains("allexchange") &&
                     settings.contains("saveoldversion") &&
                     settings.contains("restartOS") &&
                     settings.contains("pause_s") &&
                     settings.contains("filecount") &&
                     settings.contains("version");

    // Проверка типа данных для ключа "version"
    if (settings.value("version").type() != QVariant::String)
    {
        qDebug() << "Invalid data type for key 'version' in section [common]";
        hasCommon = false;
    }

    // Проверка типа данных для ключа "filecount"
    if (settings.value("filecount").type() != QVariant::Int)
    {
        qDebug() << "Invalid data type for key 'filecount' in section [common]";
        hasCommon = false;
    }

    // Проверка типа данных для ключа "pause_s"
    if (settings.value("pause_s").type() != QVariant::Int)
    {
        qDebug() << "Invalid data type for key 'pause_s' in section [common]";
        hasCommon = false;
    }

    settings.endGroup();

    // Проверка секций [file1], [file2], ..., [fileN]
    bool hasFileSections = true;
    int fileCount = settings.value("common/filecount").toInt();
    for (int i = 1; i <= fileCount; ++i)
    {
        QString group = QString("file%1").arg(i);
        settings.beginGroup(group);
        bool hasFile = settings.contains("id") &&
                       settings.contains("filename") &&
                       settings.contains("crc32Decrypt") &&
                       settings.contains("crc32Encrypt") &&
                       settings.contains("destdir");

        // Проверка типа данных для ключа "id"
        if (settings.value("id").type() != QVariant::Int)
        {
            qDebug() << "Invalid data type for key 'id' in section [" << group << "]";
            hasFile = false;
        }

        // Проверка типа данных для ключа "filename"
        if (settings.value("filename").type() != QVariant::String)
        {
            qDebug() << "Invalid data type for key 'filename' in section [" << group << "]";
            hasFile = false;
        }

        // Проверка типа данных для ключа "crc32Decrypt"
        if (settings.value("crc32Decrypt").type() != QVariant::String)
        {
            qDebug() << "Invalid data type for key 'crc32Decrypt' in section [" << group << "]";
            hasFile = false;
        }

        // Проверка типа данных для ключа "crc32Encrypt"
        if (settings.value("crc32Encrypt").type() != QVariant::String)
        {
            qDebug() << "Invalid data type for key 'crc32Encrypt' in section [" << group << "]";
            hasFile = false;
        }

        // Проверка типа данных для ключа "destdir"
        if (settings.value("destdir").type() != QVariant::String)
        {
            qDebug() << "Invalid data type for key 'destdir' in section [" << group << "]";
            hasFile = false;
        }

        hasFileSections = hasFileSections && hasFile;
        settings.endGroup();
    }

    return hasCommon && hasFileSections;
}