#include <maps/wikimap/mapspro/services/social/src/libs/feedback-actions/presets.h>
#include <maps/wikimap/mapspro/services/social/src/libs/feedback-actions/tasks_helpers.h>
#include <maps/wikimap/mapspro/services/social/src/libs/feedback-actions/tasks_regions.h>
#include <maps/wikimap/mapspro/services/social/src/libs/feedback/common.h>
#include <maps/wikimap/mapspro/services/social/src/libs/feedback/serialize.h>
#include <maps/wikimap/mapspro/services/social/src/libs/feedback/user_processing_level.h>
#include <maps/wikimap/mapspro/services/social/src/libs/yacare/error.h>
#include <maps/wikimap/mapspro/libs/acl/include/aclgateway.h>
#include <maps/wikimap/mapspro/libs/acl/include/exception.h>
#include <maps/libs/enum_io/include/enum_io.h>
#include <maps/libs/log8/include/log8.h>

#include <unicode/unistr.h>

namespace maps::wiki::socialsrv {

namespace sf = social::feedback;

namespace {

const int MAX_PRESET_NAME_LEN = 64;
const int MAX_PRESETS_NUM = 100;

std::string makeRoleName(const std::string& presetName, sf::RoleKind kind)
{
    return "feedbackPreset_" + presetName + "_" + std::string(toString(kind));
}

std::string makeRoleDescription(const std::string& presetName, sf::RoleKind kind)
{
    return "Autogenerated role. Do not edit/delete it manually."
           " " + std::string(toString(kind)) + " role for feedback preset '" + presetName + "'.";
}

void updateRole(acl::ACLGateway& aclGateway, const std::string& presetName, social::TId roleId, sf::RoleKind kind)
{
    if (roleId) {
        auto role = aclGateway.role(roleId);
        role.setName(makeRoleName(presetName, kind));
        role.setDescription(makeRoleDescription(presetName, kind));
    }
}

void deleteRole(acl::ACLGateway& aclGateway, social::TId roleId, social::TId presetId)
{
    if (roleId) try {
        auto role = aclGateway.role(roleId);
        aclGateway.drop(std::move(role));
    } catch (const acl::RoleNotExists) {
        WARN() << "Role for preset " << presetId << "didn't exist";
    }
}

void checkNameIsNotLarge(const std::string& presetName)
{
    REQUIRE(
        icu::UnicodeString::fromUTF8(presetName).length() <= MAX_PRESET_NAME_LEN,
        yacare::errors::BadRequest()
            << "Preset name '" << presetName << "' is too large"
    );
}

} // namespace

namespace internal {

sf::PresetEntries presetEntriesFromJson(const json::Value& json)
{
    sf::PresetEntries entries;

    if (json.hasField(jsids::TYPES)) {
        entries.types = asVector<sf::Type>(json[jsids::TYPES]);
    }
    if (json.hasField(jsids::WORKFLOWS)) {
        entries.workflows = asVector<sf::Workflow>(json[jsids::WORKFLOWS]);
    }
    if (json.hasField(jsids::SOURCES)) {
        entries.sources = asVector<std::string>(json[jsids::SOURCES]);
    }
    if (json.hasField(jsids::AGE_TYPES)) {
        entries.ageTypes = asVector<sf::AgeType>(json[jsids::AGE_TYPES]);
    }
    if (json.hasField(jsids::HIDDEN)) {
        entries.hidden = json[jsids::HIDDEN].as<bool>();
    }
    if (json.hasField(jsids::STATUS)) {
        entries.status = boost::lexical_cast<sf::UIFilterStatus>(
            json[jsids::STATUS].as<std::string>());
    }

    return entries;
}

} // namespace internal

sf::TaskFilter assignedPresetFilter(
    const sf::Preset& preset,
    social::TId aoiId)
{
    sf::TaskFilter taskFilter;
    auto baseDimensions = preset.getBaseDimensions();
    taskFilter.workflows(baseDimensions.getWorkflows());
    taskFilter.types(baseDimensions.getTypes());
    taskFilter.sources(baseDimensions.getSources());
    taskFilter.hidden(baseDimensions.getHidden());
    taskFilter.uiFilterStatus(std::nullopt);
    taskFilter.aoiId(aoiId);
    return taskFilter;
}

std::string createPreset(
    const maps::json::Value& jsonPreset,
    WriteContext&& writeContext)
{
    auto& socialTxn = writeContext.socialTxn();

    SOCIAL_ASSERT(
        sf::getPresets(socialTxn).size() < MAX_PRESETS_NUM,
        PresetsLimitExceed
    );

    sf::NewPreset newPreset;
    newPreset.name = jsonPreset[jsids::NAME].as<std::string>();
    newPreset.entries = internal::presetEntriesFromJson(jsonPreset);

    checkNameIsNotLarge(newPreset.name);

    try {
        acl::ACLGateway aclGateway(writeContext.coreTxn());
        for (auto kind : enum_io::enumerateValues<sf::RoleKind>()) {
            const acl::Role role = aclGateway.createRole(
                makeRoleName(newPreset.name, kind),
                makeRoleDescription(newPreset.name, kind),
                acl::Role::Privacy::Public
            );
            newPreset.roles.setId(kind,role.id());
        }

        auto addedPreset = sf::addPreset(socialTxn, newPreset);
        auto token = writeContext.commit();
        return toJson(addedPreset, token);
    } catch (const sf::UniquePresetNameError& ex) {
        throw Error(Error::Status::PresetDuplicate) << ex.what();
    } catch (const acl::DuplicateRole& ex) {
        throw Error(Error::Status::PresetDuplicate) << ex.what();
    }
}

std::string updatePreset(
    social::TId presetId,
    const maps::json::Value& jsonPreset,
    WriteContext&& writeContext)
try {

    const auto& name = jsonPreset[jsids::NAME].as<std::string>();
    checkNameIsNotLarge(name);

    sf::Preset preset{
        .id = presetId,
        .name = name,
        .entries = internal::presetEntriesFromJson(jsonPreset)
    };

    auto& socialTxn = writeContext.socialTxn();
    sf::updatePreset(socialTxn, preset);
    const auto resultPreset = sf::getPreset(socialTxn, presetId);
    ASSERT(resultPreset);

    acl::ACLGateway aclGateway(writeContext.coreTxn());
    for (auto kind : enum_io::enumerateValues<sf::RoleKind>()) {
        updateRole(aclGateway, resultPreset->name, resultPreset->roles.getId(kind), kind);
    }
    auto token = writeContext.commit();
    return toJson(*resultPreset, token);
} catch (const sf::UniquePresetNameError& ex) {
    throw Error(Error::Status::PresetDuplicate) << ex.what();
} catch (const sf::PresetDoesntExistError& ex) {
    throw Error(Error::Status::PresetNotFound) << ex.what();
} catch (const acl::DuplicateRole& ex) {
    throw Error(Error::Status::Duplicate) << ex.what();
}

std::string deletePreset(social::TId presetId, WriteContext&& writeContext)
{
    auto& socialTxn = writeContext.socialTxn();

    const auto& preset = sf::getPreset(socialTxn, presetId);
    if (!preset) {
        return writeContext.commit();
    }
    acl::ACLGateway aclGateway(writeContext.coreTxn());
    for (auto kind : enum_io::enumerateValues<sf::RoleKind>()) {
        deleteRole(aclGateway, preset->roles.getId(kind), presetId);
    }
    sf::deletePreset(socialTxn, presetId);
    return writeContext.commit();

}

AssignedPresetsResponse getAssignedPresetsResponse(
    pqxx::transaction_base& coreTxn,
    pqxx::transaction_base& socialTxn,
    const acl_utils::FeedbackPresetChecker& fbPresetChecker,
    UserId uid)
{
    auto assignedPresetsExt = fbPresetChecker.getAssignedPresets(uid);
    auto assignedPresets = assignedPresetsExt.getAssignedPresets(acl_utils::PresetRightKind::Modify);

    social::TIds assignedAoiIds;
    std::map<social::TId, sf::Preset> affectedPresets;
    std::vector<AssignedPresetWithCounter> presetsWithCounter;
    presetsWithCounter.reserve(assignedPresets.size());
    for (const auto& assignedPreset : assignedPresets) {
        if (assignedPreset.aoiId) {
            const auto aoiId = assignedPreset.aoiId.value();
            assignedAoiIds.insert(aoiId);
            auto preset = assignedPresetsExt.requirePreset(assignedPreset.presetId);
            auto taskFilter = assignedPresetFilter(preset, aoiId);
            affectedPresets[preset.id] = std::move(preset);
            taskFilter.uiFilterStatus(sf::UIFilterStatus::Opened);
            auto totalOpened = sf::GatewayRO(socialTxn).getTotalCountOfTasks(taskFilter);
            presetsWithCounter.emplace_back(AssignedPresetWithCounter{
                assignedPreset.presetId,
                aoiId,
                totalOpened});
        }
    }

    return AssignedPresetsResponse(
        std::move(presetsWithCounter),
        std::move(affectedPresets),
        aoiRegionsFromIds(coreTxn, assignedAoiIds));
}

TaskFeedForUI getPresetsTasksFeed(
    pqxx::transaction_base& socialTxn,
    const acl_utils::FeedbackChecker& feedbackChecker,
    UserId uid,
    social::TId presetId,
    social::TId aoiId,
    const sf::TaskFeedParamsId& feedParams)
{
    auto preset = sf::getPreset(socialTxn, presetId);
    SOCIAL_REQUIRE(preset, PresetNotFound, "Preset not found");

    SOCIAL_REQUIRE(feedbackChecker.fbPresetChecker()
            .hasModifyPermission(uid, *preset, aoiId),
        Forbidden, "User has no permission for preset in this region");

    auto filter = assignedPresetFilter(*preset, aoiId);
    filter.uiFilterStatus(sf::UIFilterStatus::Opened);

    auto& aclChecker = feedbackChecker.aclChecker();
    if (!aclChecker.userHasPermission(uid, INTERNAL_CONTENT_TASK_PERMISSION)) {
        filter.internalContent(false);
    }
    if (!aclChecker.userHasPermission(uid, PROCESSING_LVL1_PERMISSION)) {
        filter.processingLvls({{sf::ProcessingLvl::Level0}});
    }

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

} // namespace maps::wiki::socialsrv
