#include "underground_worker.h"
#include <random>
#include <algorithm>
#include <numeric>
#include <chrono>
#include <thread>

using json = nlohmann::json;

UndergroundWorker::~UndergroundWorker() = default;

static std::mt19937& rng() {
    static thread_local std::mt19937 g{std::random_device{}()};
    return g;
}

int UndergroundWorker::randomInt(int lo,int hi){
    std::uniform_int_distribution<int> d(lo,hi);
    return d(rng());
}

long long UndergroundWorker::randomLL(long long lo,long long hi){
    std::uniform_int_distribution<long long> d(lo,hi);
    return d(rng());
}

std::vector<size_t> UndergroundWorker::randomIndices(size_t n,size_t k){
    std::vector<size_t> idx(n);
    std::iota(idx.begin(),idx.end(),0);
    std::shuffle(idx.begin(),idx.end(),rng());
    if(k<idx.size()) idx.resize(k);
    return idx;
}

void UndergroundWorker::run(){
    using namespace std::chrono;
    while(runningWorker){
        setCurrentStep("Process underground jobs");
        signalActivity();
        tick();
        setCurrentStep("Idle");
        for(int i=0;i<60 && runningWorker;++i){
            std::this_thread::sleep_for(seconds(1));
            signalActivity();
        }
    }
}

void UndergroundWorker::tick(){
    setCurrentStep("Fetch pending underground jobs");
    ConnectionGuard g(pool);
    auto& db=g.get();
    db.prepare("UG_SELECT_PENDING",Q_SELECT_PENDING);
    db.prepare("UG_UPDATE_RESULT",Q_UPDATE_RESULT);
    const auto rows=db.execute("UG_SELECT_PENDING");
    for(const auto& r:rows){
        try{
            auto res=executeRow(r);
            int id=std::stoi(r.at("id"));
            updateResult(id,res);
            broker.publish(json{{"event","underground_processed"},{"id",id},{"type",r.at("underground_type")}}.dump());
        }catch(const std::exception& e){
            try{
                int id=std::stoi(r.at("id"));
                updateResult(id,json{{"status","error"},{"message",e.what()}});
            }catch(...){}
        }
    }
}

std::vector<UndergroundWorker::Row> UndergroundWorker::fetchPending(){
    ConnectionGuard g(pool);
    auto& db=g.get();
    db.prepare("UG_SELECT_PENDING",Q_SELECT_PENDING);
    return db.execute("UG_SELECT_PENDING");
}

nlohmann::json UndergroundWorker::executeRow(const Row& r){
    int performerId=std::stoi(r.at("performer_id"));
    int victimId=std::stoi(r.at("victim_id"));
    std::string type=r.at("underground_type");
    std::string params=r.at("parameters");
    return handleTask(type,performerId,victimId,params);
}

nlohmann::json UndergroundWorker::handleTask(const std::string& type,int performerId,int victimId,const std::string& paramsJson){
    json p; try{ p=json::parse(paramsJson);} catch(...){ p=json::object(); }
    if(type=="spyin")              return spyIn(performerId,victimId,p);
    if(type=="assassin")           return assassin(performerId,victimId,p);
    if(type=="sabotage")           return sabotage(performerId,victimId,p);
    if(type=="corrupt_politician") return corruptPolitician(performerId,victimId,p);
    if(type=="rob")                return rob(performerId,victimId,p);
    return {{"status","unknown_type"},{"type",type}};
}

nlohmann::json UndergroundWorker::spyIn(int performerId,int victimId,const json& p){
    ConnectionGuard g(pool);
    auto& db=g.get();
    db.prepare("UG_SELECT_BY_PERFORMER",Q_SELECT_BY_PERFORMER);
    const auto rows = db.execute("UG_SELECT_BY_PERFORMER",{ std::to_string(victimId) });

    json activities = json::array();
    for(const auto& r : rows){
        json params = json::object();
        try{ params = json::parse(r.at("parameters")); }catch(...){}
        json result = nullptr;
        auto it = r.find("result_text");
        if(it != r.end()){
            try{ result = json::parse(it->second); }catch(...){}
        }
        std::string status = "pending";
        if(result.is_object()){
            if(auto s = result.find("status"); s!=result.end() && s->is_string()) {
                status = s->get<std::string>();
            } else {
                status = "done";
            }
        }
        activities.push_back({
            {"id",            std::stoi(r.at("id"))},
            {"type",          r.at("underground_type")},
            {"performed_by",  std::stoi(r.at("performer_id"))},
            {"victim_id",     std::stoi(r.at("victim_id"))},
            {"created_at",    r.at("created_at")},
            {"parameters",    params},
            {"result",        result},
            {"status",        status}
        });
    }
    return {
        {"status","success"},
        {"action","spyin"},
        {"performer_id", performerId},
        {"victim_id",    victimId},
        {"details",      p},
        {"victim_illegal_activity_count", activities.size()},
        {"victim_illegal_activities",     activities}
    };
}

nlohmann::json UndergroundWorker::assassin(int performerId,int victimId,const json& p){
    ConnectionGuard g(pool);
    auto& db=g.get();
    db.prepare("UG_SELECT_CHAR_HEALTH",Q_SELECT_CHAR_HEALTH);
    db.prepare("UG_UPDATE_CHAR_HEALTH",Q_UPDATE_CHAR_HEALTH);
    const auto rows=db.execute("UG_SELECT_CHAR_HEALTH",{std::to_string(victimId)});
    if(rows.empty()) return {{"status","error"},{"action","assassin"},{"performer_id",performerId},{"victim_id",victimId},{"message","victim_not_found"},{"details",p}};
    int current=std::stoi(rows.front().at("health"));
    std::uniform_int_distribution<int> dist(0,current);
    int new_health=dist(rng());
    db.execute("UG_UPDATE_CHAR_HEALTH",{std::to_string(victimId),std::to_string(new_health)});
    return {{"status","success"},{"action","assassin"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"previous_health",current},{"new_health",new_health},{"reduced_by",current-new_health}};
}

nlohmann::json UndergroundWorker::sabotage(int performerId,int victimId,const json& p){
    const auto target=p.value("target",std::string{});
    if(target=="house")   return sabotageHouse(performerId,victimId,p);
    if(target=="storage") return sabotageStorage(performerId,victimId,p);
    return {{"status","error"},{"action","sabotage"},{"message","unknown_target"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
}

int UndergroundWorker::getUserIdForCharacter(int characterId){
    ConnectionGuard g(pool);
    auto& db=g.get();
    db.prepare("UG_SELECT_CHAR_USER",Q_SELECT_CHAR_USER);
    const auto r=db.execute("UG_SELECT_CHAR_USER",{std::to_string(characterId)});
    if(r.empty()) return -1;
    return std::stoi(r.front().at("user_id"));
}

std::optional<UndergroundWorker::HouseConditions> UndergroundWorker::getHouseByUser(int userId){
    ConnectionGuard g(pool);
    auto& db=g.get();
    db.prepare("UG_SELECT_HOUSE_BY_USER",Q_SELECT_HOUSE_BY_USER);
    const auto r=db.execute("UG_SELECT_HOUSE_BY_USER",{std::to_string(userId)});
    if(r.empty()) return std::nullopt;
    HouseConditions h{
        std::stoi(r.front().at("id")),
        std::stoi(r.front().at("roof_condition")),
        std::stoi(r.front().at("floor_condition")),
        std::stoi(r.front().at("wall_condition")),
        std::stoi(r.front().at("window_condition"))
    };
    return h;
}

void UndergroundWorker::updateHouse(const HouseConditions& h){
    ConnectionGuard g(pool);
    auto& db=g.get();
    db.prepare("UG_UPDATE_HOUSE",Q_UPDATE_HOUSE);
    db.execute("UG_UPDATE_HOUSE",{
        std::to_string(h.id),
        std::to_string(std::clamp(h.roof,0,100)),
        std::to_string(std::clamp(h.floor,0,100)),
        std::to_string(std::clamp(h.wall,0,100)),
        std::to_string(std::clamp(h.windowc,0,100))
    });
}

nlohmann::json UndergroundWorker::sabotageHouse(int performerId,int victimId,const json& p){
    int userId=getUserIdForCharacter(victimId);
    if(userId<0) return {{"status","error"},{"action","sabotage"},{"target","house"},{"message","victim_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
    auto hopt=getHouseByUser(userId);
    if(!hopt) return {{"status","error"},{"action","sabotage"},{"target","house"},{"message","house_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
    auto h=*hopt;

    std::vector<std::string> allow;
    if(p.contains("conditions") && p["conditions"].is_array())
        for(const auto& s:p["conditions"]) if(s.is_string()) allow.push_back(s.get<std::string>());

    std::vector<std::pair<std::string,int*>> fields={
        {"roof_condition",&h.roof},
        {"floor_condition",&h.floor},
        {"wall_condition",&h.wall},
        {"window_condition",&h.windowc}
    };
    std::vector<std::pair<std::string,int*>> pool;
    for(auto& f:fields) if(allow.empty() || std::find(allow.begin(),allow.end(),f.first)!=allow.end()) pool.push_back(f);
    if(pool.empty()) return {{"status","error"},{"action","sabotage"},{"target","house"},{"message","no_conditions_selected"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};

    size_t k=static_cast<size_t>(randomInt(1,(int)pool.size()));
    std::vector<size_t> picks=randomIndices(pool.size(),k);

    json changed=json::array();
    for(size_t i: picks){
        int& cur=*pool[i].second;
        if(cur>0){
            int red=randomInt(1,cur);
            cur=std::clamp(cur-red,0,100);
        }
        changed.push_back(pool[i].first);
    }

    updateHouse(h);
    return {
        {"status","success"},
        {"action","sabotage"},
        {"target","house"},
        {"performer_id",performerId},
        {"victim_id",victimId},
        {"details",p},
        {"changed_conditions",changed},
        {"new_conditions",{
            {"roof_condition",h.roof},
            {"floor_condition",h.floor},
            {"wall_condition",h.wall},
            {"window_condition",h.windowc}
        }}
    };
}

std::vector<UndergroundWorker::Row> UndergroundWorker::selectStockByBranch(int branchId){
    ConnectionGuard g(pool);
    auto& db=g.get();
    db.prepare("UG_SELECT_STOCK_BY_BRANCH",Q_SELECT_STOCK_BY_BRANCH);
    return db.execute("UG_SELECT_STOCK_BY_BRANCH",{std::to_string(branchId)});
}

std::vector<UndergroundWorker::Row> UndergroundWorker::filterByStockTypes(const std::vector<Row>& rows,const std::vector<int>& allowed){
    if(allowed.empty()) return rows;
    std::vector<Row> out;
    out.reserve(rows.size());
    for(const auto& r:rows){
        int t=std::stoi(r.at("stock_type_id"));
        if(std::find(allowed.begin(),allowed.end(),t)!=allowed.end()) out.push_back(r);
    }
    return out;
}

void UndergroundWorker::updateStockQty(int id,long long qty){
    ConnectionGuard g(pool);
    auto& db=g.get();
    db.prepare("UG_UPDATE_STOCK_QTY",Q_UPDATE_STOCK_QTY);
    db.execute("UG_UPDATE_STOCK_QTY",{std::to_string(id),std::to_string(qty)});
}

nlohmann::json UndergroundWorker::sabotageStorage(int performerId,int victimId,const json& p){
    if(!p.contains("branch_id") || !p["branch_id"].is_number_integer())
        return {{"status","error"},{"action","sabotage"},{"target","storage"},{"message","branch_id_required"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
    int branchId=p["branch_id"].get<int>();

    std::vector<int> allowed;
    if(p.contains("stock_type_ids") && p["stock_type_ids"].is_array())
        for(const auto& v:p["stock_type_ids"]) if(v.is_number_integer()) allowed.push_back(v.get<int>());

    auto rows=filterByStockTypes(selectStockByBranch(branchId),allowed);
    if(rows.empty()) return {{"status","success"},{"action","sabotage"},{"target","storage"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};

    long long total=0;
    for(const auto& r:rows) total+=std::stoll(r.at("quantity"));
    if(total<=0) return {{"status","success"},{"action","sabotage"},{"target","storage"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};

    long long cap=total/4;
    if(cap<=0) return {{"status","success"},{"action","sabotage"},{"target","storage"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};

    long long to_remove=randomLL(1,cap);
    std::shuffle(rows.begin(),rows.end(),rng());

    json affected=json::array();
    for(const auto& r:rows){
        if(to_remove==0) break;
        int id=std::stoi(r.at("id"));
        long long q=std::stoll(r.at("quantity"));
        if(q<=0) continue;
        long long take=randomLL(1,std::min(q,to_remove));
        long long newq=q-take;
        updateStockQty(id,newq);
        to_remove-=take;
        affected.push_back({{"id",id},{"stock_type_id",std::stoi(r.at("stock_type_id"))},{"previous_quantity",q},{"new_quantity",newq},{"removed",take}});
    }

    long long removed=0;
    for(const auto& a:affected) removed+=a.at("removed").get<long long>();

    return {
        {"status","success"},
        {"action","sabotage"},
        {"target","storage"},
        {"performer_id",performerId},
        {"victim_id",victimId},
        {"details",p},
        {"removed_total",removed},
        {"affected_rows",affected}
    };
}

nlohmann::json UndergroundWorker::corruptPolitician(int performerId,int victimId,const json& p){
    return {{"status","success"},{"action","corrupt_politician"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};
}

nlohmann::json UndergroundWorker::rob(int performerId,int victimId,const json& p){
    int userId=getUserIdForCharacter(victimId);
    if(userId<0) return {{"status","error"},{"action","rob"},{"message","victim_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};

    ConnectionGuard g(pool);
    auto& db=g.get();
    db.prepare("UG_SELECT_FALUKANT_USER",Q_SELECT_FALUKANT_USER);
    const auto fu=db.execute("UG_SELECT_FALUKANT_USER",{std::to_string(userId)});
    if(fu.empty()) return {{"status","error"},{"action","rob"},{"message","falukant_user_not_found"},{"performer_id",performerId},{"victim_id",victimId},{"details",p}};

    int falukantUserId=std::stoi(fu.front().at("id"));
    double money=std::stod(fu.front().at("money"));
    int defaultBranch=std::stoi(fu.front().at("main_branch_region_id"));

    bool stealGoods = (randomInt(0,1)==1);
    if(stealGoods){
        int branchId = p.contains("branch_id") && p["branch_id"].is_number_integer()
                       ? p["branch_id"].get<int>()
                       : defaultBranch;

        if(branchId<=0){
            return {{"status","success"},{"action","rob"},{"mode","goods"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};
        }

        auto rows = selectStockByBranch(branchId);
        if(rows.empty()){
            return {{"status","success"},{"action","rob"},{"mode","goods"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};
        }

        long long total=0;
        for(const auto& r:rows) total+=std::stoll(r.at("quantity"));
        if(total<=0){
            return {{"status","success"},{"action","rob"},{"mode","goods"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"removed_total",0},{"affected_rows",json::array()}};
        }

        long long cap = std::max<long long>(1, total/2);
        long long to_remove = randomLL(1, cap);

        std::shuffle(rows.begin(),rows.end(),rng());
        json affected = json::array();
        for(const auto& r:rows){
            if(to_remove==0) break;
            int id=std::stoi(r.at("id"));
            long long q=std::stoll(r.at("quantity"));
            if(q<=0) continue;
            long long take=randomLL(1,std::min(q,to_remove));
            long long newq=q-take;
            updateStockQty(id,newq);
            to_remove-=take;
            affected.push_back({
                {"id",id},
                {"stock_type_id",std::stoi(r.at("stock_type_id"))},
                {"previous_quantity",q},
                {"new_quantity",newq},
                {"removed",take}
            });
        }

        long long removed=0;
        for(const auto& a:affected) removed+=a.at("removed").get<long long>();

        return {
            {"status","success"},
            {"action","rob"},
            {"mode","goods"},
            {"performer_id",performerId},
            {"victim_id",victimId},
            {"details",p},
            {"removed_total",removed},
            {"affected_rows",affected}
        };
    } else {
        if(money<=0.0){
            return {{"status","success"},{"action","rob"},{"mode","money"},{"performer_id",performerId},{"victim_id",victimId},{"details",p},{"stolen",0.0},{"balance_before",0.0},{"balance_after",0.0}};
        }

        double rate = randomDouble(0.0,0.18);
        double amount = std::floor(money * rate * 100.0 + 0.5) / 100.0;
        if(amount < 0.01) amount = 0.01;
        if(amount > money) amount = money;

        json msg = {
            {"event","money_changed"},
            {"reason","robbery"},
            {"delta",-amount},
            {"performer_id",performerId},
            {"victim_id",victimId}
        };
        changeFalukantUserMoney(falukantUserId, -amount, "robbery", msg);

        double after = std::floor((money - amount) * 100.0 + 0.5)/100.0;

        return {
            {"status","success"},
            {"action","rob"},
            {"mode","money"},
            {"performer_id",performerId},
            {"victim_id",victimId},
            {"details",p},
            {"stolen",amount},
            {"rate",rate},
            {"balance_before",money},
            {"balance_after",after}
        };
    }
}

void UndergroundWorker::updateResult(int id,const nlohmann::json& result){
    ConnectionGuard g(pool);
    auto& db=g.get();
    db.prepare("UG_UPDATE_RESULT",Q_UPDATE_RESULT);
    db.execute("UG_UPDATE_RESULT",{std::to_string(id),result.dump()});
}

double UndergroundWorker::randomDouble(double lo,double hi){
    std::uniform_real_distribution<double> d(lo,hi);
    return d(rng());
}
