#include "director_worker.h"
#include <iostream>

DirectorWorker::DirectorWorker(ConnectionPool &pool, MessageBroker &broker)
    : Worker(pool, broker, "DirectorWorker") {
}

DirectorWorker::~DirectorWorker() {
}

void DirectorWorker::run() {
    auto lastExecutionTime = std::chrono::steady_clock::now();

    while (runningWorker) {
        signalActivity();
        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - lastExecutionTime).count();
        if (elapsed >= 60) {
            try {
                performTask();
                paySalary();
                calculateSatisfaction();
                lastExecutionTime = now;
            } catch (const std::exception &e) {
                std::cerr << "[DirectorWorker] Fehler beim Ausführen der Aufgabe: " << e.what() << std::endl;
            }
        }
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

void DirectorWorker::performTask() {
    try {
        setCurrentStep("Get Database Connection");
        ConnectionGuard connGuard(pool);
        auto &db = connGuard.get();
        setCurrentStep("Get director actions");
        db.prepare("QUERY_GET_DIRECTORS", QUERY_GET_DIRECTORS);
        const auto directors = db.execute("QUERY_GET_DIRECTORS");
        // Use const references and string_view for better performance
        for (const auto &director: directors) {
            const auto& mayProduce = director.at("may_produce");
            const auto& mayTransport = director.at("may_start_transport");
            const auto& maySell = director.at("may_sell");
            
            if (mayProduce == "t") {
                startProductions(director);
            }
            if (mayTransport == "t") {
                startTransports(director);
            }
            if (maySell == "t") {
                startSellings(director);
            }
        }
    } catch (const std::exception &e) {
        std::cerr << "[DirectorWorker] Fehler bei der Datenbankoperation: " << e.what() << std::endl;
    }
}

void DirectorWorker::startProductions(std::unordered_map<std::string, std::string> director) {
    auto parseIntOrZero = [&](const std::string &s){
        if (s.empty() || s == "null") return 0;
        try {
            return std::stoi(s);
        } catch(...) {
            return 0;
        }
    };

    setCurrentStep("Get Database Connection - Production");
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();

    setCurrentStep("Get to produce");
    db.prepare("get_to_produce", QUERY_GET_BEST_PRODUCTION);
    const auto productions = db.execute("get_to_produce", { director.at("id") });
    if (productions.empty()) return;
    const auto &production = productions.at(0);

    int runningProductions = parseIntOrZero(production.at("running_productions"));
    if (runningProductions >= 2) {
        return;
    }

    setCurrentStep("Add production to DB");
    int availableStock        = parseIntOrZero(production.at("stock_size"));
    int usedStock             = parseIntOrZero(production.at("used_in_stock"));
    int freeCapacity          = availableStock - usedStock - runningProductions;

    int certificate           = parseIntOrZero(production.at("certificate"));
    int onePieceCost          = certificate * 6;
    int money                 = parseIntOrZero(production.at("money"));
    int maxMoneyProduction    = onePieceCost > 0 ? money / onePieceCost : 0;

    int toProduce = std::min(std::min(freeCapacity, maxMoneyProduction), 300);
    if (toProduce < 1) {
        return;
    }

    int falukantUserId        = parseIntOrZero(production.at("falukant_user_id"));
    int productionCost        = toProduce * onePieceCost;

    nlohmann::json msg1 = { { "event", "falukantUpdateStatus" } };
    setCurrentStep("Update money");
    changeFalukantUserMoney(falukantUserId, -productionCost, "director starts production", msg1);

    setCurrentStep("Insert production");
    db.prepare("insert_production", QUERY_INSERT_PRODUCTION);

    int remaining = toProduce;
    while (remaining > 0) {
        int batch = std::min(100, remaining);
        db.execute("insert_production", {
            production.at("branch_id"),
            production.at("product_id"),
            std::to_string(batch)
        });
        remaining -= batch;
    }

    nlohmann::json msg2 = {
        { "event",     "production_started" },
        { "branch_id", production.at("branch_id") }
    };
    sendMessageToFalukantUsers(falukantUserId, msg2);
}

void DirectorWorker::startTransports(std::unordered_map<std::string, std::string>) {
}

void DirectorWorker::startSellings(std::unordered_map<std::string, std::string> director) {
    setCurrentStep("Get Database Connection - Production");
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    setCurrentStep("Get to sell");
    db.prepare("get_to_sell", QUERY_GET_INVENTORY);
    const auto inventory = db.execute("get_to_sell", { director.at("id") });
    for (const auto &item: inventory) {
        const auto inventoryId = std::stoi(item.at("id"));
        const auto productId = std::stoi(item.at("product_id"));
        const auto quantity = std::stoi(item.at("quantity"));
        const auto quality = std::stoi(item.at("quality"));
        const auto maxSellPrice = std::stod(item.at("sell_cost"));
        auto falukantUserId = std::stoi(item.at("user_id"));
        const auto regionId = std::stoi(item.at("region_id"));
        if (quantity > 0) {
            const auto minPrice = maxSellPrice * 0.6;
            const auto pieceSellPrice = minPrice + (double)(maxSellPrice - minPrice) * (quality / 100.0);
            const auto sellPrice = pieceSellPrice * quantity;
            const nlohmann::json changeMessage = {
                { "productId", productId },
                { "event", "falukantUpdateStatus" }
            };
            changeFalukantUserMoney(falukantUserId, sellPrice, "sell products", changeMessage);
            db.prepare("QUERY_ADD_SELL_LOG", QUERY_ADD_SELL_LOG);
            db.execute("QUERY_ADD_SELL_LOG", { std::to_string(regionId), std::to_string(productId), std::to_string(quantity),
                       std::to_string(falukantUserId) });
        }
        db.prepare("remove_inventory", QUERY_REMOVE_INVENTORY);
        db.execute("remove_inventory", { std::to_string(inventoryId) });
        nlohmann::json message = {
            { "event", "selled_items" },
            { "branch_id", item.at("branch_id") },
        };
        sendMessageToFalukantUsers(falukantUserId, message);
    }
}

void DirectorWorker::paySalary() {
    setCurrentStep("salary - load to pay");
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    db.prepare("QUERY_GET_SALARY_TO_PAY", QUERY_GET_SALARY_TO_PAY);
    const auto &salariesToPay = db.execute("QUERY_GET_SALARY_TO_PAY");
    nlohmann::json message = {
        { "event", "falukantUpdateStatus" }
    };
    for (auto const &item: salariesToPay) {
        changeFalukantUserMoney(std::stoi(item.at("employer_user_id")), -std::stoi(item.at("income")), "director payed out", message);
        db.prepare("QUERY_SET_SALARY_PAYED", QUERY_SET_SALARY_PAYED);
        db.execute("QUERY_SET_SALARY_PAYED", { std::to_string(std::stoi(item.at("id"))) });
    }
}

void DirectorWorker::calculateSatisfaction() {
    ConnectionGuard connGuard(pool);
    auto &db = connGuard.get();
    db.prepare("QUERY_UPDATE_SATISFACTION", QUERY_UPDATE_SATISFACTION);
    const auto &changedDirectors = db.execute("QUERY_UPDATE_SATISFACTION");
    nlohmann::json message = {
        { "event", "directorchanged" }
    };
    for (auto const &director: changedDirectors) {
        sendMessageToFalukantUsers(std::stoi(director.at("employer_user_id")), message);
    }
}
