#include "Application.h"
#include "AsyncEvent.h"
#include "BoostLog.h"
#include "CategoryLogSinkBackend.h"
#include "CdcUpdater.h"
#include "Configuration.h"
#include "Database.h"
#include "DateTime.h"
#include "ImageDecoder.h"
#include "StringUtility.h"
#include "VideoFrameProvider.h"
#include "VideoPlayer.h"
#include <QFile>
#include <QFont>
#include <QGuiApplication>
#include <QImage>
#include <QQmlApplicationEngine>
#include <QSerialPortInfo>
#include <QTimer>
#include <filesystem>
#include <fstream>
#include <mbedtls/md5.h>
#include "DeviceDiscovery.h"

constexpr uint32_t ImageSliceSize = (4000 - 32);

Application::Application(int &argc, char **argv) : m_app(std::make_shared<QGuiApplication>(argc, argv)) {
    m_app->setApplicationName(APPLICATION_NAME);
    m_app->setApplicationVersion(QString("v%1_%2 build: %3 %4").arg(APP_VERSION, GIT_COMMIT_ID, __DATE__, __TIME__));
    QFont font;
    font.setPointSize(12);
    m_app->setFont(font);

    m_verifyTimer = new QTimer(this);
    m_verifyTimer->setSingleShot(true);
    connect(m_verifyTimer, &QTimer::timeout, this, &Application::onVerifyTimeout);

    m_database = std::make_shared<Database>();
    QTimer::singleShot(0, this, [this]() {
        if (!m_database->open("database.db")) {
            LOG(error) << "open database failed.";
        }
    });

    m_videoFrameProvider = new VideoFrameProvider();
    if (!std::filesystem::exists(JpgPath)) {
        std::filesystem::create_directory(JpgPath);
    }
    if (!std::filesystem::exists(YuvPath)) {
        std::filesystem::create_directory(YuvPath);
    }
}

void Application::onNewEnrollResult(uint16_t userid) {
    m_palmId = userid;
    QTimer::singleShot(0, this, [this, userid]() {
        emit newStatusTip(Info, "录入成功", QString("用户: %1, ID: %2").arg(m_palmUsername).arg(userid));
    });
}

void Application::onNewVerifyResult(uint16_t userid, const QString &username, uint16_t elapsed) {
    m_palmUsername = username;
    m_palmId = userid;
    QTimer::singleShot(0, this, [this, userid, username, elapsed]() {
        emit newStatusTip(Info, QString("%1,识别耗时: %2ms")
                                    .arg(userid == ModuleCommunication::InvalidUserId ? "未录入用户" : username)
                                    .arg(elapsed));
    });
}

void Application::initializeLogger() {
    auto backend = boost::make_shared<CategoryLogSinkBackend>("GUI", [this](const std::string &log) {
        Amass::executeAtObjectThread(this, [this, log]() { emit newLog(QString::fromStdString(log)); });
    });
    using SynchronousCategorySink = boost::log::sinks::synchronous_sink<CategoryLogSinkBackend>;
    auto sink = boost::make_shared<SynchronousCategorySink>(backend);
    boost::log::core::get()->add_sink(sink);
}

int Application::exec() {
    QQmlApplicationEngine engine;
    engine.addImageProvider("videoframe", m_videoFrameProvider);
    const QUrl url(QStringLiteral("qrc:/qt/qml/Analyser/qml/Main.qml"));
    QObject::connect(
        &engine, &QQmlApplicationEngine::objectCreationFailed, m_app.get(), []() { QCoreApplication::exit(-1); },
        Qt::QueuedConnection);
    engine.load(url);

    return m_app->exec();
}

Application *Application::create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) {
    Application *ret = nullptr;
    auto app = Amass::Singleton<Application>::instance();
    if (app) {
        ret = app.get();
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        QJSEngine::setObjectOwnership(ret, QJSEngine::CppOwnership);
#endif
    }
    return ret;
}

QStringList Application::availableSerialPorts() const {
    QStringList ret;
    auto ports = QSerialPortInfo::availablePorts();
    auto iterator = std::find_if(ports.cbegin(), ports.cend(), [](const QSerialPortInfo &info) {
        return (info.productIdentifier() == ModuleCommunication::ProductIdentifier) &&
               (info.vendorIdentifier() == ModuleCommunication::VendorIdentifier);
    });
    if (iterator != ports.cend()) {
        ret << iterator->portName();
        ports.erase(iterator);
    }
    for (auto &port : ports) {
        if (port.description() == "蓝牙链接上的标准串行") continue;
        ret << port.portName();
    }
    return ret;
}

QStringList Application::availableUsbVideoCameras() const {
    QStringList ret;
    DeviceDiscovery d;
    auto devices = d.devices();
    for (auto &device : devices) {
        ret << QString::fromStdString(device);
    }
    return ret;
}

bool Application::open(const QString &portName, int baudRate) {
    m_communication = std::make_shared<ModuleCommunication>();
    connect(m_communication.get(), &ModuleCommunication::commandStarted, this, &Application::onCommandStarted);
    connect(m_communication.get(), &ModuleCommunication::commandFinished, this, &Application::onCommandFinished);
    connect(m_communication.get(), &ModuleCommunication::newEnrollResult, this, &Application::onNewEnrollResult);
    connect(m_communication.get(), &ModuleCommunication::newVerifyResult, this, &Application::onNewVerifyResult);
    connect(m_communication.get(), &ModuleCommunication::newPalmFeature, this, &Application::onNewPalmFeature);
    connect(m_communication.get(), &ModuleCommunication::newImageInfo, this, &Application::onNewImageInfo);
    connect(m_communication.get(), &ModuleCommunication::newImageSliceData, this, &Application::onNewImageSliceData);
    connect(m_communication.get(), &ModuleCommunication::errorOccurred, this, &Application::onErrorOccurred);
    emit connectedChanged();

    auto status = m_communication->open(portName, baudRate);
    emit newStatusTip(status ? Tip : Error, status ? "串口打开成功" : "串口打开失败");
    if (status) {
        QTimer::singleShot(0, this, [this]() { m_communication->requestVersion(); });
    }
    return status;
}

bool Application::openUVC(const QString &deviceName) {
    m_videoPlayer = std::make_shared<VideoPlayer>();
    m_videoPlayer->setFrameCallback([this](const QImage &image) {
        Amass::executeAtObjectThread(this, [this, image]() {
            m_videoFrameProvider->setImage(image);
            emit newVideoFrame();
        });
    });
    m_videoPlayer->setErrorCallback([this](int error, const std::string &message) {
        LOG_CAT(info, GUI) << "UVC错误: " << message;
        Amass::executeAtObjectThread(this, [this, error]() {
            if (error == -EIO) {
                closeUVC();
            }
        });
    });

    bool status = m_videoPlayer->open(deviceName.toStdString());
    if (!status) {
        m_videoPlayer.reset();
    }
    emit uvcOpenedChanged();
    emit newStatusTip(status ? Tip : Error, status ? "UVC打开成功" : "UVC打开失败");
    return status;
}

void Application::close() {
    m_communication.reset();
    emit connectedChanged();

    m_persistenceModeStarted = false;
    m_verifyTimer->stop();
    emit isVerifyingChanged();
}

void Application::closeUVC() {
    m_videoFrameProvider->reset();
    emit newVideoFrame();

    m_videoPlayer.reset();
    emit uvcOpenedChanged();
}

void Application::verify(bool captureImage, uint8_t timeout) {
    if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
        m_communication->reset();
    }
    if (captureImage) {
        m_communication->verifyExtended(captureImage, timeout);
    } else {
        m_communication->verify(timeout);
    }
    m_verifyExtendedMode = captureImage;
}

void Application::enroll(const QString &username, bool strictMode, uint8_t excludeMode, bool persistence,
                         uint8_t timeout) {
    if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
        m_communication->reset();
    }
    m_communication->enroll(username.toStdString(), strictMode, excludeMode, persistence, timeout);
    m_palmUsername = username;
}

void Application::deleteUser(uint16_t userid) {
    if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
        m_communication->reset();
    }
    m_communication->deleteUser(userid);
}

void Application::deleteAll() {
    if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
        m_communication->reset();
    }
    m_communication->deleteAll();
}

void Application::enrollExtended(const QString &username, bool strictMode, uint8_t excludeMode, bool persistence,
                                 uint8_t timeout) {
    if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
        m_communication->reset();
    }
    m_communication->enrollExtended(username.toStdString(), strictMode, excludeMode, persistence, timeout);
    m_palmUsername = username;
}

void Application::uploadImage(const QString &path, const QString &username, int operation) {
    m_uploadImageSendedSize = 0;
    ModuleCommunication::UploadImageInformation request;
    if (path.endsWith(".jpg")) {
        auto image = ImageDecoder::extractJpegYComponent(Amass::StringUtility::UTF8ToGBK(path.toStdString()));
        if (!image) {
            LOG(error) << "decode failed.";
            return;
        }
        m_uploadBuffer = image->data;
        LOG(info) << path.toStdString() << ", width: " << image->width << ", height: " << image->height;
        request.width = image->width;
        request.height = image->height;
    } else if (path.endsWith(".yuv")) {
        std::ifstream ifs(Amass::StringUtility::UTF8ToGBK(path.toStdString()), std::ofstream::binary);
        m_uploadBuffer = std::vector<uint8_t>((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
        if (m_uploadBuffer.empty()) {
            LOG(error) << "file is empty.";
        }
        request.width = 600;
        request.height = 800;
    } else {
        LOG(error) << "not supported format.";
        return;
    }

    mbedtls_md5_context context;
    mbedtls_md5_init(&context);
    mbedtls_md5_starts(&context);
    mbedtls_md5_update(&context, m_uploadBuffer.data(), m_uploadBuffer.size());
    mbedtls_md5_finish(&context, request.md5);
    mbedtls_md5_free(&context);

    request.operation = operation;
    request.size = m_uploadBuffer.size();
    strncpy(request.username, username.toStdString().c_str(), sizeof(request.username));
    m_communication->uploadImageInfo(request);
    LOG(info) << "upload image, md5: "
              << ModuleCommunication::protocolDataFormatString(request.md5, sizeof(request.md5));
    m_imageUploadling = true;
    m_uploadPath = path;
    m_uploadUsername = username;
    m_currentUploadOperation = operation;
    m_startUploadTime = std::chrono::system_clock::now();
}

ModuleCommunication *Application::module() const {
    return m_communication.get();
}

bool Application::connected() const {
    return static_cast<bool>(m_communication);
}

bool Application::uvcOpened() const {
    return static_cast<bool>(m_videoPlayer);
}

bool Application::persistenceMode() const {
    return m_persistenceMode;
}

void Application::setPersistenceMode(bool enabled) {
    if (m_persistenceMode != enabled) {
        m_persistenceMode = enabled;
        emit persistenceModeChanged();
    }
}

bool Application::imageUploadPersistenceMode() const {
    return m_imageUploadPersistenceMode;
}

void Application::setImageUploadPersistenceMode(bool enabled) {
    if (m_imageUploadPersistenceMode != enabled) {
        m_imageUploadPersistenceMode = enabled;
        emit imageUploadPersistenceModeChanged();
    }
}

int Application::persistenceVerifyInterval() const {
    return m_persistenceVerifyInterval;
}

void Application::setPersistenceVerifyInterval(int interval) {
    if (m_persistenceVerifyInterval != interval) {
        m_persistenceVerifyInterval = interval;
        emit persistenceVerifyIntervalChanged();
    }
}

bool Application::isVerifying() const {
    if (!m_communication) {
        return false;
    }
    return (m_persistenceMode && m_persistenceModeStarted) ||
           (m_communication->currentMessageId() == ModuleCommunication::Verify) ||
           (m_communication->currentMessageId() == ModuleCommunication::VerifyExtended);
}

void Application::onNewPalmFeature(const PalmFeature &feature) {
    auto palms = m_database->palmFeatures();
    if (std::find(palms.cbegin(), palms.cend(), feature) != palms.cend()) {
        LOG(warning) << "本地数据库已有相同特征数据。";
        return;
    }
    if (!m_database->addPalmFeature(feature)) {
        LOG(error) << "add palm feature failed.";
    }
}

void Application::onErrorOccurred(ModuleCommunication::NoteId note, const QString &error,
                                  const QString &detailMessage) {
    TipType type = Tip;
    if (note == ModuleCommunication::NoteId::DeviceError) {
        type = Error;
        QTimer::singleShot(0, this, [this]() { close(); });
    } else if (note == ModuleCommunication::NoteId::InteractWarning) {
        type = Warnging;
    } else {
        type = Warnging;
    }
    emit newStatusTip(type, error, detailMessage);
}

void Application::onNewImageInfo(ModuleCommunication::MessageId messageId, uint32_t size, const uint8_t *md5) {
    using namespace std::chrono;
    m_palmImageId = messageId;
    m_palmImageSize = size;
    m_communication->requestEnrolledImage(0, ImageSliceSize);
    m_palmYImageBuffer.clear();
    m_startUploadTime = system_clock::now();
    if (messageId == ModuleCommunication::Note) {
        emit newStatusTip(Error, "活体未通过照片");
    }
}

void Application::onNewImageSliceData(const std::vector<uint8_t> &data) {
    using namespace std::chrono;
    // LOG(info) << "onNewImageSliceData:" << data.size() << ", already received: " << m_palmYImageBuffer.size()
    //           << ", total size: " << m_palmImageSize;
    m_palmYImageBuffer.append(reinterpret_cast<const char *>(data.data()), data.size());
    if (m_palmYImageBuffer.size() < m_palmImageSize) {
        m_communication->requestEnrolledImage(m_palmYImageBuffer.size(), ImageSliceSize);
    } else {
        auto username = m_palmUsername.toStdString();
        auto way = (m_palmImageId == ModuleCommunication::VerifyExtended) ? "verify" : "enroll";
        if (m_palmImageId == ModuleCommunication::Note) {
            way = "no_alive";
        }
        LOG(info) << "request image finished, username: " << username << ", userid: " << m_palmId
                  << ", elapsed: " << duration_cast<milliseconds>(system_clock::now() - m_startUploadTime);
        std::ostringstream oss;
        oss << YuvPath << "/" << username << "_" << m_palmId << "_" << way << "_"
            << DateTime::toString(std::chrono::system_clock::now(), "%Y%m%d%H%M%S") << ".yuv";
        std::ofstream ofs(Amass::StringUtility::UTF8ToGBK(oss.str()), std::ofstream::binary);
        ofs.write(m_palmYImageBuffer.data(), m_palmYImageBuffer.size());

        QImage image(reinterpret_cast<const uint8_t *>(m_palmYImageBuffer.data()), 600, 800, QImage::Format_Grayscale8);
        oss.str("");
        oss << JpgPath << "/" << username << "_" << m_palmId << "_" << way << "_"
            << DateTime::toString(std::chrono::system_clock::now(), "%Y%m%d%H%M%S") << ".jpg";
        image.save(QString::fromStdString(oss.str()), "jpg", 80);
    }
}

void Application::onCommandStarted(ModuleCommunication::MessageId messageId) {
    using namespace std::chrono;
    emit isVerifyingChanged();
}

void Application::onCommandFinished(ModuleCommunication::MessageId messageId,
                                    ModuleCommunication::MessageStatus status) {
    // LOG(info) << m_persistenceMode << " " << m_persistenceModeStarted << " " << m_persistenceVerifyInterval;
    using namespace std::chrono;
    if (messageId == ModuleCommunication::UploadImageInfo) {
        m_communication->uploadImageData(m_uploadImageSendedSize, m_uploadBuffer.data() + m_uploadImageSendedSize,
                                         ImageSliceSize);
    } else if (messageId == ModuleCommunication::UploadImageData) {
        m_uploadImageSendedSize += ImageSliceSize;
        if (m_uploadImageSendedSize < m_uploadBuffer.size()) {
            auto remainSize = m_uploadBuffer.size() - m_uploadImageSendedSize;
            m_communication->uploadImageData(m_uploadImageSendedSize,
                                             (const uint8_t *)m_uploadBuffer.data() + m_uploadImageSendedSize,
                                             remainSize < ImageSliceSize ? remainSize : ImageSliceSize);
            m_imageUploadling = true;
        }
        if (status != ModuleCommunication::Needmore) {
            LOG(info) << "upload image finished, status: " << static_cast<int>(status)
                      << ", elapsed: " << duration_cast<milliseconds>(system_clock::now() - m_startUploadTime);
            m_imageUploadling = false;
            if (m_imageUploadPersistenceMode) {
                QTimer::singleShot(1, this,
                                   [this]() { uploadImage(m_uploadPath, m_uploadUsername, m_currentUploadOperation); });
            }
        }
    }

    if (((messageId == ModuleCommunication::Verify) || (messageId == ModuleCommunication::VerifyExtended)) &&
        m_persistenceMode) { // 持续识别逻辑
        m_persistenceModeStarted = true;
    } else if (messageId == ModuleCommunication::Reset) {
        m_persistenceModeStarted = false;
        m_verifyTimer->stop();
    }

    if (m_persistenceMode && m_persistenceModeStarted &&
        ((messageId == ModuleCommunication::Verify) || (messageId == ModuleCommunication::VerifyExtended)) &&
        ((status == ModuleCommunication::Success) || (status == ModuleCommunication::Failed4UnknownUser) ||
         (status == ModuleCommunication::Failed4Timeout) || (status == ModuleCommunication::Failed4UnknownReason) ||
         (status == ModuleCommunication::Failed4LivenessCheck))) {
        m_verifyTimer->start(m_persistenceVerifyInterval * 1000);
    }
    emit isVerifyingChanged();
}

void Application::onVerifyTimeout() {
    if (m_verifyExtendedMode) {
        m_communication->verifyExtended(m_verifyExtendedMode, 120);
    } else {
        m_communication->verify(120);
    }
}

bool Application::startOta(const QString &path) {
    if (!QFile::exists(path)) {
        emit otaMessage("文件不存在");
        return false;
    }
    auto device = CdcUpdater::searchDevice();
    if (device) {
        LOG(info) << "device already in ota mode.";
    } else {
        if (m_communication) {
            m_communication->startOta();
        } else {
            emit otaMessage("请先打开设备");
            return false;
        }
    }
    LOG(info) << "start ota, ota path: " << path.toStdString();
    m_updater = std::make_shared<CdcUpdater>();
    connect(m_updater.get(), &CdcUpdater::deviceDiscovered, this, &Application::onCdcDeviceDiscovered);
    connect(m_updater.get(), &CdcUpdater::updateFinished, this, &Application::updateFinished);
    connect(m_updater.get(), &CdcUpdater::progressChanged, this, &Application::otaProgressChanged);
    connect(m_updater.get(), &CdcUpdater::message, this, &Application::otaMessage);

    m_updater->start(path, device ? *device : QSerialPortInfo());
    return true;
}

void Application::onCdcDeviceDiscovered(const QSerialPortInfo &info) {
    auto status = m_updater->open(info);
    LOG(info) << "open cdc port: " << info.portName().toStdString() << ", status: " << status;
    if (!status) {
        QTimer::singleShot(0, this, [this]() { m_updater->startSearchDevice(); });
    }
}
