#include <maps/wikimap/mapspro/services/social/src/libs/stats/rating_common.h>
#include <maps/wikimap/mapspro/services/social/src/api/globals.h>
#include <maps/wikimap/mapspro/services/social/src/libs/yacare/helpers.h>

#include <yandex/maps/wiki/social/edits_stat_gateway.h>
#include <yandex/maps/wiki/social/feedback/gateway_ro.h>

#include <maps/libs/json/include/builder.h>

#include <pqxx/pqxx>
#include <string>

namespace srv = maps::wiki::socialsrv;

namespace maps::wiki::socialsrv {

namespace {

const std::string CAT_GROUP_GROUPEDITS = "groupedits_group";

const std::set<std::string> UI_CAT_GROUPS {
    "rd_group",
    "cond_group",
    "ad_group",
    "bld_group",
    "addr_group",
    "entrance_group",
    "hydro_group",
    "vegetation_group",
    "relief_group",
    "transport_railway_group",
    "transport_metro_group",
    "transport_group",
    "transport_waterway_group",
    "transport_airport_group",
    "urban_group",
    "urban_roadnet_group",
    "parking_group",
    "fence_group",
    "poi_group",
    "indoor_group"
};

} // anon namespace

size_t getModifiedFeedbackCount(
    pqxx::transaction_base& txn,
    srv::UserId uid)
{
    social::feedback::TaskFilter filter;
    filter.modifiedBy(uid);
    return social::feedback::GatewayRO(txn).getTotalCountOfTasks(filter);
}

social::TotalEditsStat
getTotalEditsStat(
    pqxx::transaction_base& txn,
    srv::UserId uid)
{
    social::TotalEditsStatGateway gtw(txn);

    if (auto stat = gtw.tryLoadOne(social::table::TotalEditsStatTbl::uid == uid); stat) {
        return stat.value();
    }

    social::TotalEditsStat res;
    res.uid = uid;
    return res;
}

std::vector<social::CatGroupEditsStat>
getCatGroupsEditsStat(
    pqxx::transaction_base& txn,
    srv::UserId uid)
{
    social::CatGroupEditsStatGateway gtw(txn);
    return gtw.load(social::table::CatGroupEditsStatTbl::uid == uid);
}

using CategoryGroup = std::string;

struct UIEditsCount
{
    size_t total{};
    size_t created{};
};

struct UIEditsStat
{
    size_t totalCount{};
    size_t groupCount{};
    std::map<CategoryGroup, UIEditsCount> catGroupCounts;
};

struct UIStat
{
    srv::UserId uid{};

    UIEditsStat editStats;

    size_t resolvedFeedbackCount{};

    size_t ratingPosFull{};
    std::optional<size_t> oldRatingPosCreated{};
    std::optional<size_t> oldRatingPosEdits{};
};

UIEditsStat
composeUIEditsStat(
    const social::TotalEditsStat& totalEditsStat,
    const std::vector<social::CatGroupEditsStat>& catGroupsEditsStat)
{
    UIEditsStat stat;

    // N.B.: using V2 version of total edits counter
    //
    stat.totalCount = totalEditsStat.totalEditsV2;

    // Initialize all UI categories with zero counters
    // regardless of their existence in user stat
    //
    for (const auto& catGroup : UI_CAT_GROUPS) {
        stat.catGroupCounts.insert({catGroup, {}});
    }

    for (const auto& groupStat : catGroupsEditsStat) {
        if (UI_CAT_GROUPS.count(groupStat.categoryGroup)) {
            stat.catGroupCounts[groupStat.categoryGroup] =
            UIEditsCount{groupStat.totalCount, groupStat.createdCount};
        } else if (groupStat.categoryGroup == CAT_GROUP_GROUPEDITS) {
            stat.groupCount = groupStat.totalCount;
        }
    }

    return stat;
}

UIStat
composeUIUserStat(
    const social::TotalEditsStat& totalEditsStat,
    const std::vector<social::CatGroupEditsStat>& catGroupsEditsStat,
    size_t ratingPosFull,
    size_t resolvedFeedbackCount)
{
    UIStat stat;

    stat.uid = totalEditsStat.uid;
    stat.editStats = composeUIEditsStat(totalEditsStat, catGroupsEditsStat);

    stat.ratingPosFull = ratingPosFull;
    stat.oldRatingPosCreated = totalEditsStat.oldRatingPosCreated;
    stat.oldRatingPosEdits = totalEditsStat.oldRatingPosEdits;

    stat.resolvedFeedbackCount = resolvedFeedbackCount;

    return stat;
}

void json(const UIEditsCount& uiEditsCount, json::ObjectBuilder builder)
{
    builder["total"] << uiEditsCount.total;
    builder["new"] << uiEditsCount.created;
}

void json(const UIEditsStat& uiEditsStat, json::ObjectBuilder builder)
{
    builder["total"] << uiEditsStat.totalCount;
    builder["group"] << uiEditsStat.groupCount;
    builder["categoryGroups"] << [&](json::ObjectBuilder groupsBuilder) {
        for (const auto& pair : uiEditsStat.catGroupCounts) {
            const auto& group = pair.first;
            const auto& editsCount = pair.second;
            groupsBuilder[group] << [&](json::ObjectBuilder editsCountBuilder) {
                json(editsCount, editsCountBuilder);
            };
        }
    };
}

void json(const UIStat& uiStat, json::ObjectBuilder builder)
{
    builder["uid"] << std::to_string(uiStat.uid);
    builder["stats"] << [&](json::ObjectBuilder statsBuilder) {
        statsBuilder["totalEdits"] << uiStat.editStats.totalCount; // TODO: remove. Only for BACK COMPATIBILITY
        statsBuilder["ratingPosFull"] << uiStat.ratingPosFull;
        statsBuilder["resolvedFeedbackCount"] << uiStat.resolvedFeedbackCount;
        if (uiStat.oldRatingPosCreated) {
            statsBuilder["oldRatingPosCreated"] << uiStat.oldRatingPosCreated.value();
        }
        if (uiStat.oldRatingPosEdits) {
            statsBuilder["oldRatingPosEdits"] << uiStat.oldRatingPosEdits.value();
        }
        statsBuilder["editStats"] << [&](json::ObjectBuilder editStatsBuilder) {
            json(uiStat.editStats, editStatsBuilder);
        };
    };
}

} // namespace maps::wiki::socialsrv

namespace {

yacare::ThreadPool statsThreadPool("statsThreadPool", 1, 1024);

} // namespace

YCR_RESPOND_TO("GET /stats/$", YCR_IN_POOL(statsThreadPool), token = "")
{
    auto userId = srv::positionalParam<srv::UserId>(argv, 0);

    auto socialTxn = srv::Globals::dbPools().socialReadTxn(token);

    auto uiUserStat = srv::composeUIUserStat(
        srv::getTotalEditsStat(*socialTxn, userId),
        srv::getCatGroupsEditsStat(*socialTxn, userId),
        srv::getRatingPos(*socialTxn, srv::RatingType::Full, userId),
        srv::getModifiedFeedbackCount(*socialTxn, userId)
    );

    response << YCR_JSON(respBuilder) {
        srv::json(uiUserStat, respBuilder);
    };
}
