#include <maps/wikimap/mapspro/services/social/src/libs/feedback-actions/meta.h>
#include <maps/wikimap/mapspro/services/social/src/libs/feedback-actions/tasks_resolve.h>
#include <maps/wikimap/mapspro/services/social/src/libs/feedback/common.h>
#include <maps/wikimap/mapspro/services/social/src/libs/yacare/error.h>

#include <yandex/maps/wiki/common/robot.h>
#include <yandex/maps/wiki/social/feedback/commits.h>
#include <yandex/maps/wiki/social/gateway.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>

namespace maps::wiki::socialsrv {

namespace mwsf = social::feedback;

namespace {

bool isFeedbackShouldBeVerified(
    const mwsf::Task& fbTask,
    acl_utils::CachingAclChecker& aclChecker,
    pqxx::transaction_base& socialTxn,
    acl::UID uid)
{
    static const std::set<mwsf::Type> TYPES_TO_SKIP {
        mwsf::Type::AddressExperiment,
        mwsf::Type::EntranceExperiment,
        mwsf::Type::RoadClosure
    };
    static const std::set<std::string> SOURCES_TO_SKIP {
        "experiment-recommendations",
        "experiment-onfoot",
        "onfoot"
    };
    if (TYPES_TO_SKIP.count(fbTask.type()) || SOURCES_TO_SKIP.count(fbTask.source())) {
        return false;
    }
    if (!mwsf::commitIdsByTaskId(socialTxn, fbTask.id()).empty()) {
        return false;
    }
    if (aclChecker.userHasPermission(uid, PROCESSING_LVL1_PERMISSION)) {
        return false;
    }
    return true;
}

PostTaskResult resolveTaskInternal(
    WriteContext& writeContext,
    const acl_utils::FeedbackChecker& feedbackChecker,
    social::TId taskId,
    social::TUid uid,
    social::feedback::Verdict verdict,
    std::optional<social::feedback::RejectReason> rejectReasonOpt,
    const std::string& message)
{
    auto& coreTxn = writeContext.coreTxn();
    auto& socialTxn = writeContext.socialTxn();

    mwsf::Agent agent(socialTxn, uid);
    const auto task = getTaskForUpdate(agent, taskId);
    const auto history = agent.gatewayRo().history(taskId);
    std::optional<mwsf::Task> modifiedTask;

    if (verdict == mwsf::Verdict::Accepted) {
        checkPermissionForTask(feedbackChecker, uid, task, history, mwsf::TaskOperation::Accept);
        if (isFeedbackShouldBeVerified(task, feedbackChecker.aclChecker(), socialTxn, uid)) {
            modifiedTask = agent.changeTaskProcessingLvl(task, mwsf::ProcessingLvl::Level1, verdict);
        } else {
            modifiedTask = agent.resolveTaskCascade(task, mwsf::Resolution::createAccepted());
        }
    } else { // Rejected
        checkPermissionForTask(feedbackChecker, uid, task, history, mwsf::TaskOperation::Reject);
        if (isFeedbackShouldBeVerified(task, feedbackChecker.aclChecker(), socialTxn, uid)) {
            modifiedTask = agent.changeTaskProcessingLvl(task, mwsf::ProcessingLvl::Level1, verdict);
        } else {
            REQUIRE(
                !agent.fromFbapi(task) || rejectReasonOpt,
                yacare::errors::BadRequest() << "Reject reason is required"
            );

            if (rejectReasonOpt) {
                feedbackChecker.aclChecker().checkPermission(uid, TASK_REJECT_REASON_PERMISSION);
            }

            static const mwsf::RejectReasonsSet REJECT_REASONS_REQUIRE_MESSAGE = [&]() {
                mwsf::RejectReasonsSet result;
                result.insert(REDIRECT_TO_CONTENT.begin(), REDIRECT_TO_CONTENT.end());
                result.insert(REDIRECT_TO_PLATFORM.begin(), REDIRECT_TO_PLATFORM.end());
                result.insert(REDIRECT_TO_OTHER.begin(), REDIRECT_TO_OTHER.end());
                return result;
            }();

            std::optional<social::TId> commentId;
            if (rejectReasonOpt && REJECT_REASONS_REQUIRE_MESSAGE.contains(*rejectReasonOpt)) {
                REQUIRE(!message.empty(), yacare::errors::BadRequest() << "message in body required");
                social::Gateway socialGtw(socialTxn);
                const auto comment = socialGtw.createComment(
                    uid, social::CommentType::Info, message, 0, 0, taskId, {});
                commentId = comment.id();
            }

            modifiedTask = agent.resolveTaskCascade(
                task, mwsf::Resolution::createRejected(rejectReasonOpt), commentId);
        }
    }
    ASSERT(modifiedTask);
    auto taskExtended = makeTaskExtended(
        std::move(modifiedTask), feedbackChecker, coreTxn, socialTxn, uid);

    auto token = writeContext.commit();
    return PostTaskResult{std::move(taskExtended), std::move(token)};
}

std::string serializeContext(
    std::chrono::seconds interval,
    social::TId taskId,
    social::feedback::Verdict verdict,
    std::optional<social::feedback::RejectReason> rejectReasonOpt,
    const std::string& message)
{
    std::ostringstream params;
    params << interval.count() << "." << taskId << "." << verdict;
    if (rejectReasonOpt) {
        params << "." << *rejectReasonOpt;
    }
    if (!message.empty()) {
        params << "." << message;
    }
    return params.str();
}

} // namespace

PostTaskResult resolveTask(
    DbPools& dbPools,
    const acl_utils::FeedbackChecker& feedbackChecker,
    social::TId taskId,
    social::TUid uid,
    social::feedback::Verdict verdict,
    std::optional<social::feedback::RejectReason> rejectReasonOpt,
    const std::string& message)
{
    auto writeContext = dbPools.writeContext();
    return resolveTaskInternal(
        writeContext, feedbackChecker, taskId, uid, verdict, rejectReasonOpt, message);
}

PostTaskResult resolveTask(
    DbPools& dbPools,
    const acl_utils::FeedbackChecker& feedbackChecker,
    const social::RateLimiter& rateLimiter,
    social::TId taskId,
    social::TUid uid,
    social::feedback::Verdict verdict,
    std::optional<social::feedback::RejectReason> rejectReasonOpt,
    const std::string& message)
{
    auto writeContext = dbPools.writeContext();
    if (!common::isRobot(uid)) {
        auto& socialTxn = writeContext.socialTxn();
        social::Gateway gateway(socialTxn);
        const auto interval = rateLimiter.checkLimitExceeded(
            gateway, uid, social::ActivityType::FeedbackResolve);
        if (interval) {
            static const std::string ERROR_MESSAGE = "Too many feedback resolve operations";
            const auto contextStr =
                serializeContext(*interval, taskId, verdict, rejectReasonOpt, message);
            WARN() << ERROR_MESSAGE << ", uid: " << uid << " : " << contextStr;
            gateway.saveUserActivityAlert(uid, "feedback.resolve." + contextStr);
            socialTxn.commit();

            throw Error(Error::Status::FeedbackResolveLimitExceeded) << ERROR_MESSAGE;
        }
    }

    return resolveTaskInternal(
        writeContext, feedbackChecker, taskId, uid, verdict, rejectReasonOpt, message);
}

} // namespace maps::wiki::socialsrv
