#include "usercharacterworker.h"
#include "connection_guard.h"
#include <iostream>
#include <chrono>
#include <thread>
#include <vector>
#include <cmath>
#include "utils.h"

UserCharacterWorker::UserCharacterWorker(ConnectionPool &pool, MessageBroker &broker)
    : Worker(pool, broker, "UserCharacterWorker"),
      gen(rd()), dist(0.0, 1.0) {}

UserCharacterWorker::~UserCharacterWorker() {}

void UserCharacterWorker::run() {
    using namespace std::chrono;

    auto lastExecutionTime = steady_clock::now();
    int lastPregnancyDay = -1;

    while (runningWorker) {
        signalActivity();

        // 1h-Block
        auto nowSteady = steady_clock::now();
        auto elapsed   = duration_cast<seconds>(nowSteady - lastExecutionTime).count();
        if (elapsed >= 3600) {
            try {
                processCharacterEvents();
                updateCharactersMood();
                handleCredits();
            } catch (const std::exception &e) {
                std::cerr << "[UserCharacterWorker] Fehler in processCharacterEvents: " << e.what() << std::endl;
            }
            lastExecutionTime = nowSteady;
        }

        // Schwangerschaftsverarbeitung: initial oder täglich um 06:00 einmal pro Tag
        auto nowSys = system_clock::now();
        std::time_t t = system_clock::to_time_t(nowSys);
        std::tm local_tm;
        localtime_r(&t, &local_tm);

        if (lastPregnancyDay == -1 || (local_tm.tm_hour == 6 && local_tm.tm_yday != lastPregnancyDay)) {
            try {
                processPregnancies();
            } catch (const std::exception &e) {
                std::cerr << "[UserCharacterWorker] Fehler in processPregnancies: " << e.what() << std::endl;
            }
            lastPregnancyDay = local_tm.tm_yday;
        }

        std::this_thread::sleep_for(seconds(1));
        recalculateKnowledge();
    }
}

void UserCharacterWorker::processCharacterEvents() {
    setCurrentStep("Get character data");
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    db.prepare(QUERY_GET_USERS_TO_UPDATE, QUERY_GET_USERS_TO_UPDATE);
    auto rows = db.execute(QUERY_GET_USERS_TO_UPDATE);
    std::vector<Character> characters;
    for (const auto &row : rows) {
        characters.push_back({ std::stoi(row.at("id")), std::stoi(row.at("age")), std::stoi(row.at("health")) });
    }
    for (auto &character : characters) {
        updateCharacterHealth(character);
    }
}

void UserCharacterWorker::updateCharacterHealth(Character& character) {
    int healthChange = calculateHealthChange(character.age);

    if (healthChange != 0) {
        character.health = std::max(0, character.health + healthChange);
        if (character.health == 0) {
            handleCharacterDeath(character.id);
            return;
        }
        ConnectionGuard connGuard(pool);
        auto &db = connGuard.get();
        db.prepare("QUERY_UPDATE_CHARACTERS_HEALTH", QUERY_UPDATE_CHARACTERS_HEALTH);
        db.execute("QUERY_UPDATE_CHARACTERS_HEALTH",
                   { std::to_string(character.health), std::to_string(character.id) });
    }
}

void UserCharacterWorker::updateCharactersMood() {
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    db.prepare("QUERY_UPDATE_MOOD", QUERY_UPDATE_MOOD);
    db.execute("QUERY_UPDATE_MOOD");
}

int UserCharacterWorker::calculateHealthChange(int age) {
    if (age < 30) {
        return 0;
    }
    if (age >= 45) {
        double probability = std::min(1.0, 0.1 + (age - 45) * 0.02);
        if (dist(gen) < probability) {
            return -std::uniform_int_distribution<int>(1, 10)(gen);
        }
        return 0;
    }
    double probability = (age - 30) / 30.0;
    return (dist(gen) < probability) ? -1 : 0;
}

void UserCharacterWorker::handleCharacterDeath(int characterId) {
    setHeir(characterId);

    nlohmann::json deathEvent = {
        {"event", "CharacterDeath"},
        {"character_id", characterId}
    };

    broker.publish(deathEvent.dump());

    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();

    try {
        // 1) Director löschen (falls Character ein Director ist)
        db.prepare("delete_director", QUERY_DELETE_DIRECTOR);
        db.execute("delete_director", { std::to_string(characterId) });

        // 2) Relationships löschen (Ehepartner, etc.)
        db.prepare("delete_relationship", QUERY_DELETE_RELATIONSHIP);
        db.execute("delete_relationship", { std::to_string(characterId) });

        // 3) Child-Relations löschen (als Kind, Vater oder Mutter)
        db.prepare("delete_child_relation", QUERY_DELETE_CHILD_RELATION);
        db.execute("delete_child_relation", { std::to_string(characterId) });

        // 4) Knowledge löschen
        db.prepare("delete_knowledge", QUERY_DELETE_KNOWLEDGE);
        db.execute("delete_knowledge", { std::to_string(characterId) });

        // 5) Debtors_prism löschen
        db.prepare("delete_debtors_prism", QUERY_DELETE_DEBTORS_PRISM);
        db.execute("delete_debtors_prism", { std::to_string(characterId) });

        // 6) Political Office löschen
        db.prepare("delete_political_office", QUERY_DELETE_POLITICAL_OFFICE);
        db.execute("delete_political_office", { std::to_string(characterId) });

        // 7) Election Candidate löschen
        db.prepare("delete_election_candidate", QUERY_DELETE_ELECTION_CANDIDATE);
        db.execute("delete_election_candidate", { std::to_string(characterId) });

        // 8) Character löschen
        db.prepare("delete_character", "DELETE FROM falukant_data.character WHERE id = $1");
        db.execute("delete_character", { std::to_string(characterId) });
    } catch (const std::exception &e) {
        std::cerr << "[UserCharacterWorker] Fehler beim Löschen der Character-Verknüpfungen: " 
                  << e.what() << std::endl;
    }
}

void UserCharacterWorker::setHeir(int characterId) {
    auto falukantUserId = getFalukantUserId(characterId);
    auto heirId = getHeirFromChildren(characterId);
    auto newMoney = calculateNewMoney(falukantUserId, true);
    if (heirId < 1) {
        getRandomHeir(characterId);
        newMoney = calculateNewMoney(falukantUserId, false);
    }
    setNewCharacter(falukantUserId, heirId);
    setNewMoney(falukantUserId, newMoney);
}

int UserCharacterWorker::getFalukantUserId(int characterId) {
    ConnectionGuard guard(pool);
    auto &db = guard.get();

    db.prepare("QUERY_GET_FALUKANT_USER_ID", QUERY_GET_FALUKANT_USER_ID);
    const auto rows = db.execute("QUERY_GET_FALUKANT_USER_ID", { std::to_string(characterId) });
    if (!rows.empty() && !rows.front().at("user_id").empty()) {
        return std::stoi(rows.front().at("user_id"));
    }
    return -1;
}

int UserCharacterWorker::getHeirFromChildren(int deceasedCharacterId) {
    ConnectionGuard guard(pool);
    auto &db = guard.get();

    db.prepare("QUERY_GET_HEIR", QUERY_GET_HEIR);
    const auto rows = db.execute("QUERY_GET_HEIR", { std::to_string(deceasedCharacterId) });
    if (!rows.empty()) {
        return std::stoi(rows.front().at("child_character_id"));
    }
    return -1;
}

int UserCharacterWorker::getRandomHeir(int deceasedCharacterId) {
    ConnectionGuard guard(pool);
    auto &db = guard.get();

    db.prepare("QUERY_RANDOM_HEIR", QUERY_RANDOM_HEIR);
    const auto rows = db.execute("QUERY_RANDOM_HEIR", { std::to_string(deceasedCharacterId) });
    if (!rows.empty()) {
        return std::stoi(rows.front().at("child_character_id"));
    }
    return -1;
}

void UserCharacterWorker::setNewCharacter(int falukantUserId, int heirCharacterId) {
    if (heirCharacterId < 1) return;

    ConnectionGuard guard(pool);
    auto &db = guard.get();

    db.prepare("QUERY_SET_CHARACTER_USER", QUERY_SET_CHARACTER_USER);
    db.execute("QUERY_SET_CHARACTER_USER", {
        std::to_string(falukantUserId),
        std::to_string(heirCharacterId)
    });
}

void UserCharacterWorker::setNewMoney(int falukantUserId, double newAmount) {
    ConnectionGuard guard(pool);
    auto &db = guard.get();

    db.prepare("QUERY_UPDATE_USER_MONEY", QUERY_UPDATE_USER_MONEY);
    db.execute("QUERY_UPDATE_USER_MONEY", {
        std::to_string(newAmount),
        std::to_string(falukantUserId)
    });
}

void UserCharacterWorker::recalculateKnowledge() {
    setCurrentStep("Get character data");
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    db.prepare("QUERY_UPDATE_GET_ITEMS_TO_UPDATE", QUERY_UPDATE_GET_ITEMS_TO_UPDATE);
    auto rows = db.execute("QUERY_UPDATE_GET_ITEMS_TO_UPDATE");
    for (const auto &updateItem: rows) {
        if (std::stoi(updateItem.at("quantity")) >= 10) {
            db.prepare("QUERY_UPDATE_GET_CHARACTER_IDS", QUERY_UPDATE_GET_CHARACTER_IDS);
            auto charactersData = db.execute("QUERY_UPDATE_GET_CHARACTER_IDS", { updateItem.at("producer_id") });
            for (const auto &characterRow: charactersData) {
                db.prepare("QUERY_UPDATE_KNOWLEDGE", QUERY_UPDATE_KNOWLEDGE);
                if (characterRow.at("director_id") == "") {
                    db.execute("QUERY_UPDATE_KNOWLEDGE", { characterRow.at("character_id"), updateItem.at("product_id"), "2" });
                } else {
                    db.execute("QUERY_UPDATE_KNOWLEDGE", { characterRow.at("character_id"), updateItem.at("product_id"), "1" });
                    db.execute("QUERY_UPDATE_KNOWLEDGE", { characterRow.at("director_id"), updateItem.at("product_id"), "1" });
                }
            }
        }
        db.prepare("QUERY_DELETE_LOG_ENTRY", QUERY_DELETE_LOG_ENTRY);
        db.execute("QUERY_DELETE_LOG_ENTRY", { updateItem.at("id") });
        const nlohmann::json message = {
            {"event", "knowledge_update"},
        };
        sendMessageToFalukantUsers(std::stoi(updateItem.at("producer_id")), message);
    }
}

void UserCharacterWorker::processPregnancies() {
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();

    db.prepare("QUERY_AUTOBATISM", QUERY_AUTOBATISM);
    db.execute("QUERY_AUTOBATISM");

    db.prepare("get_candidates", QUERY_GET_PREGNANCY_CANDIDATES);
    auto rows = db.execute("get_candidates");

    const nlohmann::json message = {
        {"event", "children_update"},
    };

    for (const auto &row : rows) {
        int fatherCid = Utils::optionalStoiOrDefault(row, "father_cid", -1);
        int motherCid = Utils::optionalStoiOrDefault(row, "mother_cid", -1);
        if (fatherCid < 0 || motherCid < 0) {
            continue; // ungültige Daten überspringen
        }

        int titleOfNobility = Utils::optionalStoiOrDefault(row, "title_of_nobility", 0);
        int lastName = Utils::optionalStoiOrDefault(row, "last_name", 0);
        int regionId = Utils::optionalStoiOrDefault(row, "region_id", 0);

        auto fatherUidOpt = Utils::optionalUid(row.at("father_uid"));
        auto motherUidOpt = Utils::optionalUid(row.at("mother_uid"));

        // Geschlecht zufällig
        std::string gender = (dist(gen) < 0.5) ? "male" : "female";

        db.prepare("insert_child", QUERY_INSERT_CHILD);
        auto resChild = db.execute("insert_child", {
            std::to_string(regionId),         // $1
            gender,                           // $2
            std::to_string(lastName),         // $3
            std::to_string(titleOfNobility)   // $4
        });

        if (resChild.empty()) continue;
        int childCid = Utils::optionalStoiOrDefault(resChild.front(), "child_cid", -1);
        if (childCid < 0) continue;

        db.prepare("insert_relation", QUERY_INSERT_CHILD_RELATION);
        db.execute("insert_relation", {
            std::to_string(fatherCid),
            std::to_string(motherCid),
            std::to_string(childCid)
        });

        if (fatherUidOpt) {
            sendMessageToFalukantUsers(*fatherUidOpt, message);
            // Sende falukantUpdateStatus nach dem Erstellen des Kindes
            nlohmann::json updateMessage = { { "event", "falukantUpdateStatus" } };
            sendMessageToFalukantUsers(*fatherUidOpt, updateMessage);
        }
        if (motherUidOpt) {
            sendMessageToFalukantUsers(*motherUidOpt, message);
            // Sende falukantUpdateStatus nach dem Erstellen des Kindes
            nlohmann::json updateMessage = { { "event", "falukantUpdateStatus" } };
            sendMessageToFalukantUsers(*motherUidOpt, updateMessage);
        }
    }
}

void UserCharacterWorker::handleCredits() {
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    db.prepare("QUERY_GET_OPEN_CREDITS", QUERY_GET_OPEN_CREDITS);
    const auto &credits = db.execute("QUERY_GET_OPEN_CREDITS");
    const nlohmann::json message = {
        { "event", "falukantUpdateStatus" }
    };
    db.prepare("QUERY_UPDATE_CREDIT", QUERY_UPDATE_CREDIT);
    db.prepare("QUERY_ADD_CHARACTER_TO_DEBTORS_PRISM", QUERY_ADD_CHARACTER_TO_DEBTORS_PRISM);
    for (const auto &credit: credits) {
        const auto userMoney = std::stod(credit.at("money"));
        auto remainingAmount = std::stod(credit.at("remaining_amount"));
        const auto amount = std::stod(credit.at("amount"));
        const auto fee = std::stoi(credit.at("interest_rate"));
        const auto falukantUserId = std::stoi(credit.at("user_id"));
        const auto payRate = amount / 10 + amount * fee / 100;
        remainingAmount -= payRate;
        if (payRate <= userMoney - (payRate * 3)) {
            changeFalukantUserMoney(falukantUserId, -payRate, "credit pay rate", message);
        } else {
            if (credit.at("prism_started_previously") == "t") {
                changeFalukantUserMoney(falukantUserId, payRate, "debitor_prism", message);
            } else {
                db.execute("QUERY_ADD_CHARACTER_TO_DEBTORS_PRISM", { credit.at("character_id") });
            }
        }
        db.execute("QUERY_UPDATE_CREDIT", { std::to_string(remainingAmount), std::to_string(falukantUserId) });
    }
    db.prepare("QUERY_CLEANUP_CREDITS", QUERY_CLEANUP_CREDITS);
    db.execute("QUERY_CLEANUP_CREDITS");
}

double UserCharacterWorker::getCurrentMoney(int falukantUserId) {
    ConnectionGuard g(pool); auto &db = g.get();
    db.prepare("GET_CURRENT_MONEY", QUERY_GET_CURRENT_MONEY);
    auto rows = db.execute("GET_CURRENT_MONEY", {std::to_string(falukantUserId)});
    return rows.empty()? 0.0 : std::stod(rows.front().at("sum"));
}

double UserCharacterWorker::getHouseValue(int falukantUserId) {
    ConnectionGuard g(pool); auto &db = g.get();
    db.prepare("HOUSE_VALUE", QUERY_HOUSE_VALUE);
    auto rows = db.execute("HOUSE_VALUE", {std::to_string(falukantUserId)});
    return rows.empty()? 0.0 : std::stod(rows.front().at("sum"));
}

double UserCharacterWorker::getSettlementValue(int falukantUserId) {
    ConnectionGuard g(pool); auto &db = g.get();
    db.prepare("SETTLEMENT_VALUE", QUERY_SETTLEMENT_VALUE);
    auto rows = db.execute("SETTLEMENT_VALUE", {std::to_string(falukantUserId)});
    return rows.empty()? 0.0 : std::stod(rows.front().at("sum"));
}

double UserCharacterWorker::getInventoryValue(int falukantUserId) {
    ConnectionGuard g(pool); auto &db = g.get();
    db.prepare("INVENTORY_VALUE", QUERY_INVENTORY_VALUE);
    auto rows = db.execute("INVENTORY_VALUE", {std::to_string(falukantUserId)});
    return rows.empty()? 0.0 : std::stod(rows.front().at("sum"));
}

double UserCharacterWorker::getCreditDebt(int falukantUserId) {
    ConnectionGuard guard(pool);
    auto &db = guard.get();

    db.prepare("CREDIT_DEBT", QUERY_CREDIT_DEBT);
    auto rows = db.execute("CREDIT_DEBT", { std::to_string(falukantUserId) });
    return rows.empty()
        ? 0.0
        : std::stod(rows.front().at("sum"));
}

int UserCharacterWorker::getChildCount(int deceasedUserId) {
    ConnectionGuard g(pool); auto &db = g.get();
    db.prepare("COUNT_CHILDREN", QUERY_COUNT_CHILDREN);
    auto rows = db.execute("COUNT_CHILDREN", {std::to_string(deceasedUserId)});
    return rows.empty()? 0 : std::stoi(rows.front().at("cnt"));
}

double UserCharacterWorker::calculateNewMoney(int falukantUserId, bool hasHeir) {
    if (!hasHeir) {
        return 800.0;
    }
    double cash   = getCurrentMoney(falukantUserId);
    double houses = getHouseValue(falukantUserId);
    double sets   = getSettlementValue(falukantUserId);
    double inv    = getInventoryValue(falukantUserId);
    double debt   = getCreditDebt(falukantUserId);
    double totalAssets = cash + houses + sets + inv - debt;
    int childCount = getChildCount(falukantUserId);
    bool single = (childCount <= 1);
    double heirShare = single ? totalAssets : totalAssets * 0.8;
    double net = heirShare - (houses + sets + inv + debt);
    if (net <= 1000.0) {
        return 1000.0;
    }
    return net;
}
