#include "DeviceScreener.h"

#include <QSettings>
#include <QProcess>
#include <QFile>
#include <QBuffer>
#include <qeventloop.h>
#include <QThread>
#include <QImage>
#include <QHttpMultiPart>
#include <QJsonObject>
#include <QJsonArray>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <dao/DeviceDAO.h>
#include <db/DeviceInfo.h>

DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) {
    const QSettings settings("config.ini", QSettings::IniFormat);
    m_urlAppUpdate = settings.value("net/appInfoUpdate").toString();
}

DeviceScreener::~DeviceScreener() = default;

void DeviceScreener::postDevicesData(const QList<DeviceInfo> &devices) {
    auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

    QHttpPart jsonPart;
    jsonPart.setHeader(QNetworkRequest::ContentTypeHeader, "application/json; charset=UTF-8");
    jsonPart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="metadata")");

    QJsonArray list;
    for (const DeviceInfo &device: devices) {
        QJsonObject obj;
        obj["uniqueName"] = device.id;
        obj["status"] = device.status;
        obj["name"] = device.name;
        obj["screenWidth"] = device.screenWidth;
        obj["screenHeight"] = device.screenHeight;
        obj["battery"] = device.battery;

        list.append(obj);
    }

    jsonPart.setBody(QJsonDocument(list).toJson());
    multiPart->append(jsonPart);


    for (const DeviceInfo &device: devices) {
        QHttpPart imagePart;
        QString imageHeader = QStringLiteral("form-data; name=files; filename=%1.jpg").arg(device.id);
        imagePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/jpeg");
        imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, imageHeader.toUtf8());
        imagePart.setBody(device.image);
        multiPart->append(imagePart);
    }

    QHttpPart uniquePart;
    uniquePart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="desktopUniqueName")");
    uniquePart.setBody("DT88bokcwQ");

    multiPart->append(uniquePart);

    QNetworkAccessManager manager; // создаём внутри потока
    QUrl url(m_urlAppUpdate);
    QNetworkRequest request(url);

    // делаем запрос
    QNetworkReply *reply = manager.post(request, multiPart);
    multiPart->setParent(reply); // multiPart удалится, когда придёт ответ

    // локальный цикл — ждём, пока придёт finished()
    QEventLoop loop;
    QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
    QObject::connect(reply, &QNetworkReply::errorOccurred, [&loop](QNetworkReply::NetworkError) {
        loop.quit();
    });
    loop.exec(); // <- здесь и запускается event-loop потока

    // обрабатываем результат
    if (reply->error() == QNetworkReply::NoError) {
        QByteArray raw = reply->readAll();
        QJsonParseError err;
        QJsonDocument doc = QJsonDocument::fromJson(raw, &err);
        if (err.error == QJsonParseError::NoError && doc.isObject()) {
            QJsonObject root = doc.object();
            if (!root.value("success").toBool()) {
                qWarning() << root;
            }
        }
    } else {
        qWarning() << "Response error:" << reply->errorString() << m_urlAppUpdate;
    }
    reply->deleteLater();
}

void DeviceScreener::start() {
    while (m_running) {
        QList<QPair<QString, QString> > devices = readAdbDevices();
        QStringList onlineIds;
        QList<DeviceInfo> deviceInfos;
        for (const auto &[id, status]: devices) {
            DeviceInfo device;
            device.id = id;


            if (status == "device") {
                onlineIds << device.id;
                device = readDeviceInfo(id);
                device.image = takeScreenshot(id);
                if (!DeviceDAO::updateImage(id, device.image)) {
                    qDebug() << "Cant update screenshot";
                }
            }
            device.status = status;
            device.updateTime = QDateTime::currentDateTime();
            deviceInfos.append(device);
            if (!DeviceDAO::upsertDevice(device)) {
                qDebug() << "Cant update devive";
            }
        }

        if (!deviceInfos.empty()) {
            postDevicesData(deviceInfos);
        }

        if (!DeviceDAO::markDevicesOfflineExcept(onlineIds)) {
            qDebug() << "Cant mark devices offline";
        }
        QThread::msleep(1000);
    }
    emit finished();
}

QByteArray convertPngToJpeg(const QByteArray &pngData, const int quality = 90) {
    QImage image;
    if (!image.loadFromData(pngData, "PNG")) {
        qWarning() << "Не удалось загрузить PNG из байт";
        return {};
    }

    QByteArray jpgData;
    QBuffer buffer(&jpgData);
    buffer.open(QIODevice::WriteOnly);
    image.save(&buffer, "JPG", quality);
    return jpgData;
}

QByteArray DeviceScreener::takeScreenshot(const QString &deviceId) {
    QProcess process;
    QStringList arguments;

    arguments << "-s" << deviceId << "exec-out" << "screencap" << "-p";
    process.start("adb", arguments);

    if (!process.waitForFinished(5000)) {
        qWarning() << "ADB скриншот не завершился для" << deviceId;
        return {};
    }

    const QByteArray imageData = process.readAllStandardOutput();

    if (imageData.isEmpty()) {
        qWarning() << "Скриншот пустой для " << deviceId;
        return {};
    }

    return convertPngToJpeg(imageData, 90);
}

QList<QPair<QString, QString> > DeviceScreener::readAdbDevices() {
    QProcess process;
    process.start("adb", QStringList() << "devices");
    process.waitForFinished();

    const QString output = process.readAllStandardOutput();
    QList<QPair<QString, QString> > devices;
    const QStringList lines = output.split('\n');
    for (int i = 1; i < lines.size(); ++i) {
        QString line = lines.at(i).trimmed();

        if (line.isEmpty()) {
            continue;
        }
        /**
        * device — устройство готово к использованию.
        * offline — устройство подключено, но не может быть использовано.
        * unauthorized — устройство не авторизовано для работы с ADB (нужно подтвердить запрос на подключение на самом устройстве).
        * recovery — устройство в режиме восстановления.
        */
        // Разделяем строку на id и type по табуляции
        if (QStringList parts = line.split('\t'); parts.size() == 2) {
            const QString &id = parts.at(0);
            const QString &status = parts.at(1);
            devices.append(qMakePair(id, status));
        }
    }
    return devices;
}

static const QRegularExpression screenRegex(R"(Physical size: (\d+)x(\d+))");
static const QRegularExpression batteryRegex(R"(level:\s*(\d+))");

DeviceInfo DeviceScreener::readDeviceInfo(const QString &deviceId) {
    DeviceInfo info;
    info.id = deviceId;
    info.screenWidth = 0;
    info.screenHeight = 0;
    info.battery = 0;

    auto runAdbCommand = [&](const QStringList &args) -> QString {
        QProcess process;
        process.start("adb", QStringList{"-s", deviceId} + args);
        process.waitForFinished();
        return QString::fromUtf8(process.readAllStandardOutput()).trimmed();
    };

    info.name = runAdbCommand({"shell", "getprop", "ro.product.model"});
    info.android = runAdbCommand({"shell", "getprop", "ro.build.version.release"});

    // Размер экрана
    const QString screenOutput = runAdbCommand({"shell", "wm", "size"});
    QRegularExpressionMatch match = screenRegex.match(screenOutput);
    if (match.hasMatch()) {
        info.screenWidth = match.captured(1).toInt();
        info.screenHeight = match.captured(2).toInt();
    }

    // Уровень батареи
    const QString batteryOutput = runAdbCommand({"shell", "dumpsys", "battery"});
    match = batteryRegex.match(batteryOutput);
    if (match.hasMatch()) {
        info.battery = match.captured(1).toInt();
    }

    info.updateTime = QDateTime::currentDateTime();
    return info;
}


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