#include "get_exclusions.h"
#include <maps/wikimap/mapspro/services/editor/src/configs/config.h>
#include "maps/wikimap/mapspro/services/editor/src/check_permissions.h"
#include <maps/wikimap/mapspro/services/editor/src/utils.h>

#include <yandex/maps/wiki/common/paged_result.h>
#include <yandex/maps/wiki/revision/branch_manager.h>
#include <yandex/maps/wiki/revision/revisionsgateway.h>
#include <yandex/maps/wiki/validator/validator.h>
#include <yandex/maps/wiki/validator/storage/exclusions_gateway.h>

#include <map>

namespace maps::wiki {

namespace vs = validator::storage;

namespace {

using validator::Severity;
using validator::TCheckId;

const std::string BASE_CHECK_ID = "base";

const std::string STR_SEVERITY = "severity";
const std::string STR_CHECK = "check";
const std::string STR_DESCRIPTION = "description";
const std::string STR_REGION_TYPE = "region-type";

typedef std::map<TCheckId, std::map<std::string, size_t>> CountsByCheckByDescription;

ValidationStatsNode
checkStatsNode(const CountsByCheckByDescription& counts, const TCheckId& checkId)
{
    std::vector<ValidationStatsNode> descriptionStats;
    size_t totalCount = 0;
    auto it = counts.find(checkId);
    if (it != counts.end()) {
        for (const auto& count : it->second) {
            descriptionStats.push_back(
                    ValidationStatsNode{STR_DESCRIPTION, count.first, count.second, {}});
            totalCount += count.second;
        }
    }
    return ValidationStatsNode{STR_CHECK, checkId, totalCount, descriptionStats};
}

} // namespace

GetValidationExclusions::GetValidationExclusions(
        const Request& request)
    : controller::BaseController<GetValidationExclusions>(BOOST_CURRENT_FUNCTION)
    , request_(request)
{
    result_->page = request_.page;
    result_->perPage = request_.perPage;
    result_->totalCount = 0;
}

std::string
GetValidationExclusions::printRequest() const
{
    std::stringstream ss;
    ss << " uid: " << request_.uid
       << " branch: " << request_.branchId;

    if (request_.severity) {
        ss << " severity: " << *request_.severity;
    }
    if (request_.checkId) {
        ss << " check: " << *request_.checkId;
    }
    if (request_.description) {
        ss << " description: " << *request_.description;
    }
    if (request_.regionType) {
        ss << " region-type: " << *request_.regionType;
    }
    if (request_.createdBy) {
        ss << " created-by: " << *request_.createdBy;
    }
    if (request_.bboxStr) {
        ss << " bb: " << *request_.bboxStr;
    }

    ss << " page: " << request_.page
       << " per-page: " << request_.perPage
       << " token: " << request_.token;
    return ss.str();
}

void
GetValidationExclusions::control()
{
    auto workCore = cfg()->poolCore().slaveTransaction(request_.token);
    CheckPermissions(
            request_.uid,
            *workCore).checkUserHasAccessToValidatorTasks();
    auto branch = revision::BranchManager(*workCore).load(request_.branchId);

    auto workValidation = cfg()->poolValidation().masterReadOnlyTransaction();

    revision::RevisionsGateway revGw(*workCore, branch);
    auto snapshot = revGw.snapshot(revGw.headCommitId());

    auto exclGw = vs::ExclusionsGateway(*workValidation);

    vs::ExclusionsFilter filter;
    filter.attributes.severity = request_.severity;
    filter.attributes.checkId = request_.checkId;
    filter.attributes.description = request_.description;
    filter.attributes.regionType = request_.regionType;
    filter.createdBy = request_.createdBy;
    if (request_.viewed) {
        filter.viewedBy = vs::ViewedByFilter{
            request_.uid,
            *request_.viewed ? vs::ViewedState::Viewed : vs::ViewedState::NotViewed
        };
    }
    if (request_.bboxStr) {
        filter.bbox = createBbox(
                *request_.bboxStr, SpatialRefSystem::Geodetic);
    }

    vs::ExclusionsFilter statsFilter;
    statsFilter.createdBy = filter.createdBy;
    statsFilter.bbox = filter.bbox;

    auto statistics = exclGw.statistics(statsFilter);

    for (const auto& stat : statistics) {
        if (filter.attributes.matches(stat.first)) {
            result_->totalCount += stat.second;
        }
    }

    result_->regionTypeStats = regionTypeStatsNodes(statistics);
    result_->severityStats = severityStatsNodes(statistics);
    result_->checkStats = checkStatsNodes(statistics);

    result_->uid = request_.uid;

    if (request_.limit) {
        auto result = exclGw.exclusions(
            filter,
            snapshot,
            request_.startId,
            request_.beforeAfter,
            request_.limit);
        result_->exclusions = std::move(result.exclusions);
        result_->hasMore = result.hasMore;
    } else {
        common::Pager pager(result_->totalCount, request_.page, request_.perPage);
        result_->exclusions = exclGw.exclusions(
            filter, snapshot, pager.offset(), pager.limit());
    }
}

std::vector<ValidationStatsNode>
GetValidationExclusions::regionTypeStatsNodes(const vs::MessageStatistics& statistics)
{
    std::map<validator::RegionType, size_t> countsByRegionType;
    for (const auto& stat : statistics) {
        countsByRegionType[stat.first.regionType] += stat.second;
    }

    std::vector<ValidationStatsNode> result;
    for (auto regionType : {validator::RegionType::Important, validator::RegionType::Unimportant}) {
        result.push_back(
            ValidationStatsNode{
                    STR_REGION_TYPE,
                    boost::lexical_cast<std::string>(regionType),
                    countsByRegionType[regionType],
                    {}});
    }
    return result;
}

std::vector<ValidationStatsNode>
GetValidationExclusions::severityStatsNodes(const vs::MessageStatistics& statistics)
{
    std::map<int, size_t> countsBySeverity;
    for (const auto& stat : statistics) {
        if (!request_.regionType || request_.regionType == stat.first.regionType) {
            countsBySeverity[static_cast<int>(stat.first.severity)] += stat.second;
        }
    }

    std::vector<ValidationStatsNode> result;
    for (int i = static_cast<int>(validator::Severity::Min);
         i <= static_cast<int>(validator::Severity::Max);
         ++i) {

        Severity severity = static_cast<Severity>(i);
        result.push_back(
                ValidationStatsNode{
                        STR_SEVERITY,
                        boost::lexical_cast<std::string>(severity),
                        countsBySeverity[i],
                        {}});
    }
    return result;
}

std::vector<ValidationStatsNode>
GetValidationExclusions::checkStatsNodes(const vs::MessageStatistics& statistics)
{
    CountsByCheckByDescription countsByCheckByDescription;
    for (const auto& stat : statistics) {
        if ((!request_.severity || request_.severity == stat.first.severity)
            && (!request_.regionType || request_.regionType == stat.first.regionType)) {
            countsByCheckByDescription
                [stat.first.checkId][stat.first.description]
                += stat.second;
        }
    }

    std::vector<ValidationStatsNode> result;
    result.push_back(checkStatsNode(countsByCheckByDescription, BASE_CHECK_ID));

    for (const validator::ModuleInfo& module : cfg()->validatorModules()) {
        std::vector<ValidationStatsNode> checkNodes;
        size_t totalCount = 0;
        for (const TCheckId& checkId : module.checkIds()) {
            checkNodes.push_back(checkStatsNode(countsByCheckByDescription, checkId));
            totalCount += checkNodes.back().count;
        }

        if (!checkNodes.empty()) {
            result.push_back(
                    ValidationStatsNode{STR_CHECK, module.name(), totalCount, checkNodes});
        }
    }

    return result;
}

} // namespace maps::wiki
