#include "valuerecalculationworker.h"

ValueRecalculationWorker::ValueRecalculationWorker(ConnectionPool &pool, MessageBroker &broker)
    : Worker(pool, broker, "ValueRecalculationWorker"),
      activities{
          {"productKnowledge", Activity(std::chrono::system_clock::from_time_t(0),
                                        [this]() { calculateProductKnowledge(); },
                                        std::chrono::hours(0))},  // 00:00 Uhr

          {"regionalSellPrice", Activity(std::chrono::system_clock::from_time_t(0),
                                         [this]() { calculateRegionalSellPrice(); },
                                         std::chrono::hours(12) + std::chrono::minutes(0))}  // 12:00 Uhr
      }
{
}

ValueRecalculationWorker::~ValueRecalculationWorker() {

}

void ValueRecalculationWorker::run() {
    while (runningWorker) {
        setCurrentStep("Check if activity has to run");
        auto now = std::chrono::system_clock::now();
        for (auto &[key, activity] : activities) {
            if (shouldRunToday(activity)) {
                activity.lastRun = now;
                activity.callMethod();
            }
        }
        setCurrentStep("CalculateMarriages");
        calculateMarriages();
        calculateStudying();
        setCurrentStep("Sleep for 60 seconds");
        for (int i = 0; i < 60 && runningWorker; ++i) {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            setCurrentStep("signalActivity()");
            signalActivity();
        }
        setCurrentStep("Loop done");
    }
}

bool ValueRecalculationWorker::shouldRunToday(const Activity& activity) {
    auto now = std::chrono::system_clock::now();
    auto todayScheduledTime = getNextScheduledTime(activity.scheduledTime);
    return now >= todayScheduledTime && activity.lastRun < todayScheduledTime;
}

std::chrono::system_clock::time_point ValueRecalculationWorker::getNextScheduledTime(std::chrono::system_clock::duration scheduledDuration) {
    auto now = std::chrono::system_clock::now();
    std::time_t now_c = std::chrono::system_clock::to_time_t(now);
    std::tm now_tm = *std::localtime(&now_c);
    now_tm.tm_hour = std::chrono::duration_cast<std::chrono::hours>(scheduledDuration).count();
    now_tm.tm_min = std::chrono::duration_cast<std::chrono::minutes>(scheduledDuration).count() % 60;
    now_tm.tm_sec = 0;
    return std::chrono::system_clock::from_time_t(std::mktime(&now_tm));
}

void ValueRecalculationWorker::calculateProductKnowledge() {
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    db.prepare("QUERY_UPDATE_PRODUCT_KNOWLEDGE_USER", QUERY_UPDATE_PRODUCT_KNOWLEDGE_USER);
    db.execute("QUERY_UPDATE_PRODUCT_KNOWLEDGE_USER");
    db.prepare("QUERY_GET_PRODUCERS_LAST_DAY", QUERY_GET_PRODUCERS_LAST_DAY);
    const auto &usersToInform = db.execute("QUERY_GET_PRODUCERS_LAST_DAY");
    const nlohmann::json message = {
        { "event", "price_update" }
    };
    for (const auto &user: usersToInform) {
        const auto userId = std::stoi(user.at("producer_id"));
        sendMessageToFalukantUsers(userId, message);
    }
    db.prepare("QUERY_DELETE_OLD_PRODUCTIONS", QUERY_DELETE_OLD_PRODUCTIONS);
    db.execute("QUERY_DELETE_OLD_PRODUCTIONS");
}

void ValueRecalculationWorker::calculateRegionalSellPrice() {
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    db.prepare("QUERY_UPDATE_REGION_SELL_PRICE", QUERY_UPDATE_REGION_SELL_PRICE);
    db.execute("QUERY_UPDATE_REGION_SELL_PRICE");
    db.prepare("QUERY_GET_SELL_REGIONS", QUERY_GET_SELL_REGIONS);
    const auto &regionsWithSells = db.execute("QUERY_GET_SELL_REGIONS");
    const nlohmann::json message = {
        { "event", "price_update" }
    };
    for (const auto &region: regionsWithSells) {
        const auto regionId = std::stoi(region.at("region_id"));
        sendMessageToRegionUsers(regionId, message);
    }
    db.prepare("QUERY_DELETE_REGION_SELL_PRICE", QUERY_DELETE_REGION_SELL_PRICE);
    db.execute("QUERY_DELETE_REGION_SELL_PRICE");
}

void ValueRecalculationWorker::calculateMarriages() {
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    db.prepare("QUERY_SET_MARRIAGES_BY_PARTY", QUERY_SET_MARRIAGES_BY_PARTY);
    const auto &usersFromUpdatedRelationships = db.execute("QUERY_SET_MARRIAGES_BY_PARTY");
    const nlohmann::json message = {
        { "event", "relationship_changed" }
    };
    for (const auto &userFromUpdatedRelationships: usersFromUpdatedRelationships) {
        if (userFromUpdatedRelationships.at("character1_user") != "") {
            const auto user1Id = std::stoi(userFromUpdatedRelationships.at("character1_user"));
            sendMessageToRegionUsers(user1Id, message);
        }
        if (userFromUpdatedRelationships.at("character2_user") != "") {
            const auto user2Id = std::stoi(userFromUpdatedRelationships.at("character2_user"));
            sendMessageToRegionUsers(user2Id, message);
        }
    }

}

void ValueRecalculationWorker::calculateStudying() {
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    db.prepare("QUERY_GET_STUDYINGS_TO_EXECUTE", QUERY_GET_STUDYINGS_TO_EXECUTE);
    db.prepare("QUERY_SET_LEARNING_DONE", QUERY_SET_LEARNING_DONE);
    const auto studies = db.execute("QUERY_GET_STUDYINGS_TO_EXECUTE");
    for (const auto &study: studies) {
        if (study.at("tr") == "self") {
            calculateStudyingSelf(study);
        } else if (study.at("tr") == "children" || study.at("tr") == "director") {
            caclulateStudyingForAssociatedCharacter(study);
        }
        db.execute("QUERY_SET_LEARNING_DONE", {study.at("id")});
    }
}

void ValueRecalculationWorker::calculateStudyingSelf(Database::FieldMap entry) {
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    db.prepare("QUERY_GET_OWN_CHARACTER_ID", QUERY_GET_OWN_CHARACTER_ID);
    const auto ownCharacterIdResult = db.execute("QUERY_GET_OWN_CHARACTER_ID", { entry.at("associated_falukant_user_id") });
    if (ownCharacterIdResult.size() > 0) {
        auto characterId = std::stoi(ownCharacterIdResult.at(0).at("id"));
        auto learnAll = entry.at("learn_all_products") == "t" || entry.at("product_id") == "";
        int productId = learnAll ? 0 : std::stoi(entry.at("product_id"));
        calculateStudyingCharacter(characterId, learnAll, productId, std::stoi(entry.at("learning_recipient_id")));
    }
}

void ValueRecalculationWorker::caclulateStudyingForAssociatedCharacter(Database::FieldMap entry) {
    auto characterId = std::stoi(entry.at("associated_learning_character_id"));
    auto learnAll = entry.at("learn_all_products") == "t" || entry.at("product_id") == "";
    int productId = learnAll ? 0 : std::stoi(entry.at("product_id"));
    calculateStudyingCharacter(characterId, learnAll, productId, std::stoi(entry.at("learning_recipient_id")));
}

void ValueRecalculationWorker::calculateStudyingCharacter(int characterId, bool all, int productId, int falukantUserId) {
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    if (all) {
        db.prepare("QUERY_INCREASE_ALL_PRODUCTS_KNOWLEDGE", QUERY_INCREASE_ALL_PRODUCTS_KNOWLEDGE);
        db.execute("QUERY_INCREASE_ALL_PRODUCTS_KNOWLEDGE", { "1", std::to_string(characterId) });
    } else {
        db.prepare("QUERY_INCREASE_ONE_PRODUCT_KNOWLEDGE", QUERY_INCREASE_ONE_PRODUCT_KNOWLEDGE);
        db.execute("QUERY_INCREASE_ONE_PRODUCT_KNOWLEDGE", { "5", std::to_string(characterId), std::to_string(productId) });
    }
    const nlohmann::json message = {
        { "event", "knowledge_updated" }
    };
    sendMessageToFalukantUsers(falukantUserId, message);
}

