#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/string_utils.h>
#include <maps/wikimap/mapspro/libs/acl/include/aclgateway.h>
#include <maps/wikimap/mapspro/libs/revision/include/yandex/maps/wiki/revision/revisionsgateway.h>
#include <maps/wikimap/mapspro/services/social/src/libs/feedback-actions/tasks_regions.h>

#include <future>

namespace maps::wiki::socialsrv {

namespace sf = social::feedback;

namespace {

const std::string FEEDBACK_ROLE = "feedback";
const std::string AOI_NAME = "aoi:name";

std::set<ID> aoiIdsInUse(pqxx::transaction_base& coreTxn)
{
    acl::ACLGateway aclGateway(coreTxn);
    std::set<ID> aoiIds;
    for (const auto& policy : aclGateway.role(FEEDBACK_ROLE).policies()) {
        if (policy.aoiId() != 0) {
            aoiIds.insert(policy.aoiId());
        }
    }
    return aoiIds;
}

RegionsStatRows
constructRegionsStatRows(
    const std::map<ID, AoiRegion>& regions,
    const sf::AoiToTaskStatCounters& aoiCounters,
    const sf::AoiToOptionalTask aoiOldestTasks)
{
    RegionsStatRows rows;
    for (const auto& [aoiId, region] : regions) {
        ASSERT(aoiCounters.count(aoiId));
        ASSERT(aoiOldestTasks.count(aoiId));
        rows.emplace(
            aoiId,
            RegionStatRow(
                region,
                aoiCounters.at(aoiId),
                aoiOldestTasks.at(aoiId)
            )
        );
    }
    return rows;
}

AoiRegionToTaskCounters combineRegionsWithCounters(
    std::map<ID, AoiRegion>& regions,
    const sf::AoiToTaskCounters& aoiCounters)
{
    AoiRegionToTaskCounters res;
    for (auto& [aoiId, region] : regions) {
        res[std::move(region)] = aoiCounters.at(aoiId);
    }
    return res;
}

std::set<ID> userFeedbackAoiIds(pqxx::transaction_base& coreTxn, UserId uid)
{
    acl::ACLGateway aclGateway(coreTxn);
    std::set<ID> aoiIds;
    for (const auto& policy : aclGateway.user(uid).allPolicies()) {
        if (policy.aoiId() != 0 && policy.role().name() == FEEDBACK_ROLE) {
            aoiIds.insert(policy.aoiId());
        }
    }
    return aoiIds;
}

} // unnamed namespace

std::map<ID, AoiRegion> aoiRegionsFromIds(
    pqxx::transaction_base& coreTxn,
    const std::set<ID>& aoiIds)
{
    if (aoiIds.empty()) {
        return {};
    }

    revision::RevisionsGateway revGateway(coreTxn);
    auto snapshot = revGateway.snapshot(revGateway.maxSnapshotId());

    std::map<ID, AoiRegion> aoiRegions;

    for (const auto& [id, rev] : snapshot.objectRevisions(aoiIds)) {
        const auto& attrs = rev.data().attributes;
        ASSERT(attrs);
        ASSERT(attrs->count(AOI_NAME));
        aoiRegions.emplace(id, AoiRegion(id, attrs->at(AOI_NAME)));
    }

    REQUIRE(aoiIds.size() == aoiRegions.size(),
        yacare::errors::NotFound()
        << "Some aoi not found: " << common::join(aoiIds, ","));

    return aoiRegions;
}

RegionsStatRows getRegionsStat(
    pqxx::transaction_base& coreTxn,
    std::function<pgpool3::TransactionHandle ()> socialHandleGetter,
    social::feedback::AoiTaskFilter filter)
{
    auto aoiIds = aoiIdsInUse(coreTxn);

    auto aoiRegions = std::async(std::launch::async, [&](){
        return aoiRegionsFromIds(coreTxn, aoiIds);
    });

    auto aoiTaskStatCounters = std::async(std::launch::async, [&](){
        auto socialTxn = socialHandleGetter();
        return sf::calcAoiOpenedTaskStatCounters(
            *socialTxn, aoiIds, filter);
    });

    auto aoiOldestTasks = std::async(std::launch::async, [&](){
        auto socialTxn = socialHandleGetter();
        return sf::calcAoiOldestOpenedTask(*socialTxn, aoiIds, filter);
    });

    return constructRegionsStatRows(
        aoiRegions.get(),
        aoiTaskStatCounters.get(),
        aoiOldestTasks.get()
    );
}

AoiRegionToTaskCounters getRegionsCounters(
    pqxx::transaction_base& coreTxn,
    pqxx::transaction_base& socialTxn,
    UserId uid,
    const social::feedback::TaskFilter& filter)
{
    auto aoiIds = userFeedbackAoiIds(coreTxn, uid);
    auto aoiRegions = aoiRegionsFromIds(coreTxn, aoiIds);

    auto aoiTaskCounters = sf::calcAoiTaskCounters(
        socialTxn,
        aoiIds,
        filter,
        sf::Partition::OutgoingOpened);

    return combineRegionsWithCounters(aoiRegions, aoiTaskCounters);
}

TaskFeedForUI getRegionsFeed(
    pqxx::transaction_base& socialTxn,
    ID aoiId,
    const std::set<social::feedback::Partition> partitions,
    const social::feedback::TaskFilter& filter,
    const social::feedback::TaskFeedParamsId& feedParams,
    serialize::SubstitutionStrategy substitutionStrategy)
{
    auto feed = sf::aoiTaskFeed(
        socialTxn,
        aoiId,
        feedParams,
        filter,
        partitions
    );

    sf::GatewayRO gatewayRo(socialTxn);
    return makeFeedForUI(
        gatewayRo,
        std::move(feed),
        substitutionStrategy);
}

} // namespace maps::wiki::socialsrv
