#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>

#include <maps/wikimap/mapspro/services/mrc/libs/db/include/house_number_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/object/include/revision_loader.h>
#include <maps/wikimap/mapspro/services/mrc/libs/privacy/include/region_privacy.h>
#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/extended_xml_doc.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/chrono/include/time_point.h>

#include <mapreduce/yt/interface/client.h>

#include <util/system/env.h>

using namespace NYT;

namespace {

maps::mrc::db::HouseNumberFeatures loadHouseNumberFeatures(
    maps::pgpool3::Pool& pool,
    const maps::mrc::db::TIds& houseNumberIds
) {
    auto txn = pool.slaveTransaction();
    maps::mrc::db::HouseNumberFeatures houseNumberFeatures =
        maps::mrc::db::HouseNumberFeatureGateway(*txn).load(
            maps::mrc::db::table::HouseNumberFeature::houseNumberId.in(std::move(houseNumberIds)));
    return houseNumberFeatures;
}

maps::mrc::db::HouseNumberFeatures loadHouseNumberFeatures(
    maps::pgpool3::Pool& pool,
    const maps::mrc::db::HouseNumbers& houseNumbers
) {
    maps::mrc::db::TIds houseNumberIds(houseNumbers.size());
    for (size_t i = 0; i < houseNumbers.size(); i++) {
        houseNumberIds[i] = houseNumbers[i].id();
    }
    return loadHouseNumberFeatures(pool, houseNumberIds);
}

std::map<maps::mrc::db::TId, maps::mrc::db::Feature> loadFeatures(
    maps::pgpool3::Pool& pool,
    const maps::mrc::db::TIds& featureIds
) {
    auto txn = pool.slaveTransaction();
    maps::mrc::db::Features features =
        maps::mrc::db::FeatureGateway(*txn).loadByIds(std::move(featureIds));

    std::map<maps::mrc::db::TId, maps::mrc::db::Feature> result;
    for (size_t i = 0; i < features.size(); i++) {
        result.emplace(features[i].id(), features[i]);
    }
    return result;
}

maps::mrc::db::HouseNumbers loadHouseNumbersWithFeedback(
    maps::pgpool3::Pool& pool
) {
    auto txn = pool.slaveTransaction();
    maps::mrc::db::HouseNumbers houseNumbers =
        maps::mrc::db::HouseNumberGateway(*txn)
            .load(maps::mrc::db::table::HouseNumber::feedbackTaskId > 0);
    return houseNumbers;
}

std::set<maps::mrc::db::TId> loadClosedFeedbackIds() {
    static const TString YT_PROXY = "hahn";
    static const TString YT_TABLE = "//home/maps/core/nmaps/analytics/feedback/db/feedback_latest";

    static const TString COL_ID         = "id";
    static const TString COL_TYPE       = "type";
    static const TString COL_SOURCE     = "source";
    static const TString COL_RESOLUTION = "resolution";
    static const TString COL_CREATED_AT = "created_at";

    static const TString FEEDBACK_TYPE     = "address";
    static const TString FEEDBACK_SOURCE   = "mrc";

    static const std::string CREATED_BEFORE = "2019-10-10 00:00:00.000000+03";

    maps::chrono::TimePoint createdBefore = maps::chrono::parseSqlDateTime(CREATED_BEFORE);

    std::set<maps::mrc::db::TId> feedbackIds;
    INFO() << "Connecting to yt::" << YT_PROXY;
    IClientPtr client = CreateClient(YT_PROXY);
    TTableReaderPtr<TNode> reader = client->CreateTableReader<TNode>(YT_TABLE);
    for (int processedItems = 0; reader->IsValid(); reader->Next(), processedItems++) {
        if ((processedItems + 1) % 1000000 == 0) {
            INFO() << "Processed " << (processedItems + 1) << " feedback items";
        }

        const TNode& inpRow = reader->GetRow();
        if (inpRow[COL_TYPE].AsString() != FEEDBACK_TYPE || inpRow[COL_SOURCE].AsString() != FEEDBACK_SOURCE)
            continue;

        maps::chrono::TimePoint createdAt = maps::chrono::parseSqlDateTime(inpRow[COL_CREATED_AT].AsString());
        if (createdAt < createdBefore && !inpRow[COL_RESOLUTION].IsNull())
            feedbackIds.insert(inpRow[COL_ID].AsUint64());
    };
    return feedbackIds;
}

void filterHiddenFeatures(
    maps::pgpool3::Pool& pool,
    maps::mrc::privacy::RegionPrivacyPtr& regionPrivacy,
    maps::mrc::db::HouseNumbers& houseNumbers
) {
    maps::mrc::db::HouseNumberFeatures houseNumberFeatures =
        loadHouseNumberFeatures(pool, houseNumbers);
    INFO() << "Loaded " << houseNumberFeatures.size() << " house number features";

    std::map<maps::mrc::db::TId, std::vector<maps::mrc::db::TId>>
        houseNumberIdToFeatureIds;
    maps::mrc::db::TIds featureIds(houseNumberFeatures.size());
    for (size_t i = 0; i < houseNumberFeatures.size(); i++) {
        featureIds[i] = houseNumberFeatures[i].featureId();
        houseNumberIdToFeatureIds[houseNumberFeatures[i].houseNumberId()].push_back(houseNumberFeatures[i].featureId());
    }
    INFO() << "Filled featureIds and houseNumberIdToFeatureIds";

    std::map<maps::mrc::db::TId, maps::mrc::db::Feature>
        featureIdToFeature = loadFeatures(pool, featureIds);

    INFO() << "Loaded features";
    houseNumbers.erase(
        std::remove_if(houseNumbers.begin(), houseNumbers.end(),
            [&](const maps::mrc::db::HouseNumber& houseNumber) {
                bool hidden = regionPrivacy->evalFeaturePrivacy(
                                  houseNumber.geodeticPos()) !=
                              maps::mrc::db::FeaturePrivacy::Public;
                if (!hidden) {
                    std::vector<maps::mrc::db::TId> featureIds = houseNumberIdToFeatureIds[houseNumber.id()];
                    for (maps::mrc::db::TId featureId : featureIds) {
                        const auto citFeature = featureIdToFeature.find(featureId);
                        REQUIRE(citFeature != featureIdToFeature.end(),
                            "Feature not found for Id: " << featureId);
                        if (citFeature->second.privacy() != maps::mrc::db::FeaturePrivacy::Public) {
                            hidden = true;
                            break;
                        }
                    }
                }
                return hidden;
            }
        ),
        houseNumbers.end()
    );
}

void filterClosedFeedbacks(
    maps::mrc::db::HouseNumbers& houseNumbers
) {
    std::set<maps::mrc::db::TId> closedFeedbackIds = loadClosedFeedbackIds();
    houseNumbers.erase(
        std::remove_if(houseNumbers.begin(), houseNumbers.end(),
            [&](const maps::mrc::db::HouseNumber& houseNumber) {
                return closedFeedbackIds.count(houseNumber.feedbackTaskId()) > 0;
            }
        ),
        houseNumbers.end()
    );
}

void markHouseNumbersToToloka(
    maps::pgpool3::Pool& pool,
    maps::mrc::db::HouseNumbers houseNumbers
) {
    constexpr maps::mrc::db::TId HOUSE_NUMBER_FOR_TOLOKA_FEEDBACK_TASK_ID = -3;

    auto txn = pool.masterWriteableTransaction();
    maps::mrc::db::HouseNumberGateway houseNumberGateway(*txn);
    for (size_t i = 0; i < houseNumbers.size(); i++) {
        houseNumbers[i].setFeedbackTaskId(HOUSE_NUMBER_FOR_TOLOKA_FEEDBACK_TASK_ID);
    }
    houseNumberGateway.update(houseNumbers);
    txn->commit();
}

};

int main(int argc, const char* argv[]) try {
    constexpr size_t BATCH_SIZE = 1000;

    maps::cmdline::Parser
        parser("Resend house number to toloka");

    maps::cmdline::Option<std::string> mrcConfigPath = parser.string("mrc-config")
        .required()
        .help("Path to mrc config");

    maps::cmdline::Option<std::string> wikiConfigPath = parser.string("wiki-config")
        .required()
        .help("Path to services config for wikimap");

    maps::cmdline::Option<std::string> secretVersion = parser.string("secret-version")
        .defaultValue("")
        .help("version for secrets from yav.yandex-team.ru");

    maps::cmdline::Option<std::string> geoIdCoveragePath = parser.string("geoid_coverage_path")
        .help("path to coverage for geoid");

    maps::cmdline::Option<std::string> outputFeedbackIdsPath = parser.string("feedback-ids")
        .required()
        .defaultValue("")
        .help("Path to save feedback ids for close");

    maps::cmdline::Option<bool> dryRunParam = parser.flag("dry-run")
        .help("do not save changes to database");


    parser.parse(argc, const_cast<char**>(argv));

    const maps::mrc::common::Config mrcConfig =
        maps::mrc::common::templateConfigFromCmdPath(secretVersion, mrcConfigPath);

    auto poolHolder = mrcConfig.makePoolHolder();
    maps::wiki::common::ExtendedXmlDoc wikiConfig(wikiConfigPath);
    maps::wiki::common::PoolHolder wiki(wikiConfig, "core", "tasks");
    maps::mrc::object::LoaderHolder  objectLoader = maps::mrc::object::makeRevisionLoader(wiki.pool().slaveTransaction());
    maps::mrc::privacy::RegionPrivacyPtr regionPrivacy =
        maps::mrc::privacy::makeCachingRegionPrivacy(
            *objectLoader,
            geoIdCoveragePath.defined()
                ? std::string(geoIdCoveragePath)
                : mrcConfig.externals().geoIdCoveragePath()
        );

    maps::mrc::db::HouseNumbers houseNumbers = loadHouseNumbersWithFeedback(poolHolder.pool());
    INFO() << "Load " << houseNumbers.size() << " house numbers with positive feedback_task_id";
    filterHiddenFeatures(poolHolder.pool(), regionPrivacy, houseNumbers);
    INFO() << "House numbers after filtered hidden features: " << houseNumbers.size();
    filterClosedFeedbacks(houseNumbers);
    INFO() << "House numbers after filtered closed feedbacks: " << houseNumbers.size();

    std::ofstream ofs(outputFeedbackIdsPath);
    for (size_t i = 0; i < houseNumbers.size(); i++) {
        ofs << houseNumbers[i].feedbackTaskId() << std::endl;
    }

    if (dryRunParam)
        return EXIT_SUCCESS;

    for (size_t i = 0; i < houseNumbers.size(); i+=BATCH_SIZE) {
        markHouseNumbersToToloka(
            poolHolder.pool(),
            maps::mrc::db::HouseNumbers(
                { houseNumbers.cbegin() + i,
                  houseNumbers.cbegin() + std::min(i + BATCH_SIZE, houseNumbers.size())
                }
            )
        );
    }
    return EXIT_SUCCESS;
} catch (const maps::Exception& ex) {
    std::cerr << "Failed maps::Exception: " << ex << std::endl;
    return EXIT_FAILURE;
} catch (const std::exception& ex) {
    std::cerr << "Failed std::exception: " << ex.what() << std::endl;
    return EXIT_FAILURE;
}
