#include "tasks_feed.h"

#include <maps/wikimap/mapspro/services/social/src/libs/common/common.h>

#include <maps/wikimap/mapspro/services/social/src/libs/feedback/common.h>
#include <maps/wikimap/mapspro/services/social/src/libs/feedback/user_processing_level.h>
#include <maps/wikimap/mapspro/services/social/src/libs/yacare/bbox.h>
#include <maps/wikimap/mapspro/services/social/src/libs/yacare/helpers.h>

#include <maps/wikimap/mapspro/libs/acl_utils/include/feedback.h>
#include <maps/wikimap/mapspro/libs/acl_utils/include/user_feed_access.h>

#include <yandex/maps/wiki/common/robot.h>
#include <yandex/maps/wiki/revision/revisionsgateway.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/geolib/include/serialization.h>

namespace maps::wiki::socialsrv {

namespace mwsf = social::feedback;

namespace {

bool isFeedRegion(const revision::ObjectRevision& revision)
{
    const auto& attr = revision.data().attributes;
    return attr && attr->count("cat:feed_region");
}

std::optional<geolib3::Polygon2> getBoundary(
    pgpool3::TransactionHandle& coreTxn,
    std::optional<social::TUid> feedRegionId)
{
    std::optional<geolib3::Polygon2> result;
    if (feedRegionId) {
        const auto rg = revision::RevisionsGateway(*coreTxn);
        const auto revision =
            rg.snapshot(rg.headCommitId()).objectRevision(*feedRegionId);

        SOCIAL_REQUIRE(revision && !revision->data().deleted, NotFound, "Object " << *feedRegionId << " not found.");
        SOCIAL_REQUIRE(isFeedRegion(*revision), NotFound, "Object " << *feedRegionId << " is not feed region.");

        const auto wkbGeometry = revision->data().geometry;
        SOCIAL_REQUIRE(wkbGeometry, NotFound, "Feed-region " << *feedRegionId << " has no geometry.");

        result = geolib3::WKB::read<geolib3::Polygon2>(*wkbGeometry);
    }
    return result;
}

bool isPersonalFeed(const TasksFeedParams& params, social::TUid uid)
{
    if (params.createdBy == uid) {
        return true;
    }
    if (params.acquiredBy == uid) {
        return true;
    }
    if (params.resolvedBy == uid) {
        return true;
    }
    return false;
}

} // namespace

TaskFeedForUI getTasksFeed(
    DbPools& dbPools,
    const acl_utils::FeedbackChecker& feedbackChecker,
    const TasksFeedParams& params,
    social::TUid uid,
    const DbToken& token)
{
    REQUIRE(
        !params.bboxGeo || !params.feedRegionId,
        yacare::errors::BadRequest()
            << "Mutually exclusive parameters bbox and feed-region-id are provided."
    );

    std::optional<geolib3::BoundingBox> bboxGeo;
    if (params.bboxGeo) {
        bboxGeo = bboxGeoFromRequest(*params.bboxGeo);
    }

    auto coreTxn = dbPools.coreReadTxn();
    const auto boundary = getBoundary(coreTxn, params.feedRegionId);

    auto socialTxn = dbPools.socialReadTxn(token);
    mwsf::GatewayRO gatewayRo(*socialTxn);

    // If object-id is set (such queries take place each time user
    // clicks on objects in editor) we can optimize out ACL queries for types
    // if this object doesn't have any feedback.
    // The following 'tasksByFilter' query is extremely fast and number of
    // returned tasks is small for single object
    //
    if (params.objectId) {
        auto tasks = gatewayRo.tasksByFilter(mwsf::TaskFilter()
            .objectId(*params.objectId)
            .buckets(mwsf::allRevealedBuckets()));

        if (tasks.empty()) {
            return TaskFeedForUI{{}, social::HasMore::No, 0};
        }
    }

    // ACL
    if (params.acquiredBy && *params.acquiredBy != uid) {
        feedbackChecker.aclChecker().checkPermission(uid, CLOSE_TASK_PERMISSION);
    }

    if (params.resolvedBy && !acl_utils::isUserAllowedToReadFeed(*coreTxn, uid, *params.resolvedBy)) {
        throw Error(Error::Status::Forbidden)
            << "User with uid " << uid << " is not allowed to view "
            << "feedback feed of user " << *params.resolvedBy;
    }
    const bool maySeeHidden = feedbackChecker.aclChecker().userHasPermission(uid, HIDDEN_TASK_PERMISSION);
    if (!maySeeHidden && params.hidden && *params.hidden) {
        throw Error(Error::Status::Forbidden)
            << "User with uid " << uid << " is not allowed to view "
            << "hidden feedback.";
    }

    social::TUids blockedResolvers;
    if (!params.resolvedByRobots) {
        blockedResolvers.insert(
            common::ALL_ROBOTS_UIDS.begin(), common::ALL_ROBOTS_UIDS.end());
    }

    // Construct task filter
    //
    mwsf::TaskFilter filter;
    filter.buckets(mwsf::allRevealedBuckets());

    filter.types(params.types);

    if (params.createdBy) {
        filter.createdBy(*params.createdBy);
    }
    if (params.acquiredBy) {
        filter.acquiredBy(*params.acquiredBy);
    }
    filter.resolvedBy(params.resolvedBy);
    if (!blockedResolvers.empty()) {
        filter.notResolvedBy(blockedResolvers);
    }
    if (params.duplicateHeadId) {
        if (*params.duplicateHeadId) {
            filter.duplicateHeadId(*params.duplicateHeadId);
        } else {
            filter.duplicateHeadId(std::nullopt);
        }
    }
    if (params.objectId) {
        filter.objectId(*params.objectId);
    }

    filter.uiFilterStatus(params.status);
    if (params.resolved) {
        filter.resolved(*params.resolved);
    }
    filter.workflows(params.workflows);
    filter.sources(params.sources);
    if (bboxGeo) {
        filter.boxBoundary(geolib3::convertGeodeticToMercator(*bboxGeo));
    }
    if (boundary) {
        filter.boundary(*boundary);
    }

    if (!isPersonalFeed(params, uid)) {
        const std::optional<bool> permittedHidden = maySeeHidden ? params.hidden : false;
        if (permittedHidden) {
            filter.hidden(*permittedHidden);
        }

        if (!feedbackChecker.aclChecker().userHasPermission(uid, INTERNAL_CONTENT_TASK_PERMISSION)) {
            filter.internalContent(false);
        }

        feedbackChecker.fbPresetChecker().restrictFilterByGlobalPresets(filter, uid);
    }

    // Get tasks feed
    //
    mwsf::TaskFeedParamsId feedParams(
        params.beforeId,
        params.afterId,
        params.perPage,
        social::TasksOrder::NewestFirst
    );

    auto feedWithCount = gatewayRo.tasksFeedWithCount(filter, feedParams);
    return makeFeedForUI(
        gatewayRo,
        std::move(feedWithCount),
        getSubstitutionStrategy(feedbackChecker.aclChecker(), uid),
        feedWithCount.totalCount);
}

} // namespace maps::wiki::socialsrv
