#include "EventHandler.h"

#include <QJsonObject>
#include <QMap>
#include <QThread>
#include <QTimer>

#include "dao/AccountDAO.h"
#include "dao/ApplicationDAO.h"
#include "dao/DeviceDAO.h"
#include "dao/EventDAO.h"
#include "db/ApplicationInfo.h"
#include "net/NetworkService.h"
#include "rshb/CommonScript.h"
#include "rshb/GetLastDaysHistoryScript.h"
#include "rshb/LoginAndCheckAccountsScript.h"
#include "rshb/PayByPhoneScript.h"

EventHandler::EventHandler(QObject *parent) : QObject(parent) {
    m_checkTimer = new QTimer(this);
    m_checkTimer->setInterval(1000);
    connect(m_checkTimer, &QTimer::timeout, this, &EventHandler::checkAndRestart);
}

EventHandler::~EventHandler() = default;

void EventHandler::start() {
    m_devices = DeviceDAO::getOnlineDeviceCodes();

    clearAll();
    // Запустим сразу потоки для всех ключей
    for (auto &key: m_devices) {
        startThreadForKey(key);
    }

    m_checkTimer->start();
}

void EventHandler::checkAndRestart() {
    if (!m_running) {
        return;
    }
    m_devices = DeviceDAO::getOnlineDeviceCodes();

    for (const auto &key: m_devices) {
        const QThread *thr = m_threads.value(key, nullptr);
        if (!thr || !thr->isRunning()) {
            startThreadForKey(key);
        }
    }
}

void sendEventStatus(const QString &deviceId, const int externalId, const int eventId, const QString &status,
                     const QString &error) {
    QJsonObject obj;
    obj["id"] = externalId;
    obj["externalId"] = eventId;
    obj["status"] = status;
    obj["error"] = error;

    auto *paymentThread = new QThread;
    auto *networkService = new NetworkService;
    networkService->moveToThread(paymentThread);
    networkService->setDeviceId(deviceId);
    networkService->setPayload(QJsonDocument(obj).toJson());
    QObject::connect(paymentThread, &QThread::started, networkService, &NetworkService::postEventStatus);
    QObject::connect(networkService, &NetworkService::finished, paymentThread, &QThread::quit);
    QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater);
    QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater);
    paymentThread->start();
}

void EventHandler::updateEventThread(const QString &key, const EventInfo &event, const QString &result) {
    if (!result.isEmpty()) {
        if (!EventDAO::updateEvent(event.id, EventStatus::Error, result)) {
            qCritical() << "Cant update event: " << event.id;
        } else {
            sendEventStatus(
                event.deviceId,
                event.externalId,
                event.id,
                eventStatusToString(EventStatus::Error),
                result
            );
        }
    } else {
        if (!EventDAO::updateEvent(event.id, EventStatus::Complete, "")) {
            qCritical() << "Cant update event: " << event.id;
        } else {
            sendEventStatus(
                event.deviceId,
                event.externalId,
                event.id,
                eventStatusToString(EventStatus::Complete),
                ""
            );
        }
    }

    if (const auto tm = m_timers.take(key)) {
        tm->stop();
        tm->deleteLater();
    }
    m_threads.remove(key);
}

void EventHandler::startThreadForKey(const QString &key) {
    const EventInfo event = EventDAO::getFirstWaitEventByDeviceId(key);
    if (event.id == -1) {
        return;
    }

    const AccountInfo account = AccountDAO::getAccountById(event.accountId);
    const ApplicationInfo app = ApplicationDAO::getApplication(event.deviceId, account.appCode);
    if (app.status != "active") {
        return;
    }

    if (!EventDAO::updateEvent(event.id, EventStatus::InProgress, "")) {
        qCritical() << "Cant update event: " << event.id;
        return;
    }

    const auto thr = new QThread(this);
    // qDebug() << "Start thread for key" << eventTypeToString(event.type);
    if (event.type == EventType::Balance) {
        auto *worker = new GetLastDaysHistoryScript(account);
        worker->moveToThread(thr);

        connect(thr, &QThread::started, worker, &GetLastDaysHistoryScript::start);
        connect(worker, &GetLastDaysHistoryScript::finished, thr, &QThread::quit);
        connect(worker, &GetLastDaysHistoryScript::finished, worker, &QObject::deleteLater);
        connect(thr, &QThread::finished, thr, &QObject::deleteLater);

        connect(worker, &GetLastDaysHistoryScript::finishedWithResult, this,
                [this, key, event](const QString &result) {
                    updateEventThread(key, event, result);
                }
        );
    }

    if (event.type == EventType::Phone) {
        auto *worker = new PayByPhoneScript(account, event.phone, event.bankName, event.amount);
        worker->moveToThread(thr);

        connect(thr, &QThread::started, worker, &PayByPhoneScript::start);
        connect(worker, &PayByPhoneScript::finished, thr, &QThread::quit);
        connect(worker, &PayByPhoneScript::finished, worker, &QObject::deleteLater);
        connect(thr, &QThread::finished, thr, &QObject::deleteLater);

        connect(worker, &PayByPhoneScript::finishedWithResult, this,
               [this, key, event](const QString &result) {
                   updateEventThread(key, event, result);
               }
       );
    }

    // Таймер на 5 минут — если не успел finish(), убить и перезапустить
    const auto timer = new QTimer(this);
    timer->setSingleShot(true);
    connect(timer, &QTimer::timeout, this, [this, key]() {
        qWarning() << "Timeout for key" << key << "- killing stuck thread.";
        if (QThread *thread = m_threads.value(key); thread && thread->isRunning()) {
            thread->requestInterruption(); // мягкое прерывание
            if (!thread->wait(500)) {
                qWarning() << "Hard terminate for key" << key;
                thread->terminate();
                thread->wait();
            }
        }
        if (const auto tm = m_timers.take(key)) {
            tm->deleteLater();
        }
        m_threads.remove(key);
    });

    // Сохраняем и запускаем
    m_threads[key] = thr;
    m_timers[key] = timer;

    timer->start(5 * 60 * 1000);
    thr->start();
}

void EventHandler::clearAll() {
    // Останавливаем и удаляем старые
    for (QThread *thread: m_threads) {
        if (thread->isRunning()) {
            thread->requestInterruption();
            thread->terminate();
            thread->wait();
        }
        thread->deleteLater();
    }
    m_threads.clear();

    for (QTimer *timer: m_timers) {
        timer->stop();
        timer->deleteLater();
    }
    m_timers.clear();
}

void EventHandler::stop() {
    m_running = false;
    m_checkTimer->stop();
    clearAll();
}
