#include <maps/wikimap/mapspro/services/mrc/long_tasks/regular_upload/lib/include/config.h>
#include <maps/wikimap/mapspro/services/mrc/long_tasks/regular_upload/lib/include/uploader.h>

#include <maps/libs/chrono/include/time_point.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/sql_chemistry/include/system_information.h>

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

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

#include <maps/libs/common/include/retry.h>
#include <yandex/maps/wiki/common/pgpool3_helpers.h>

#include <string>
#include <unordered_map>

namespace maps::mrc::regular_upload {

namespace {

bool isNumber(const std::string &str) {
    return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit);
}

} // nameless namespace

using Node = NYT::TNode;

RegularUploader::RegularUploader(const RegularUploadConfig& cfg) : cfg_(cfg)
{}

blackbox_client::UidToLoginMap RegularUploader::getUserLogins(
    sql_chemistry::BatchLoad<db::table::Feature>& batch
    ) const
{
    std::vector<blackbox_client::Uid> userIds;
    userIds.reserve(cfg_.uploadBatchSize());

    for (const auto& photo : batch) {
        if (photo.userId().has_value()) {
            userIds.push_back(boost::lexical_cast<blackbox_client::Uid>(
                photo.userId().value()));
        }
    }
    if (userIds.empty()) {
        return {};
    }

    const auto retryPolicy = maps::common::RetryPolicy()
        .setTryNumber(3)
        .setInitialCooldown(std::chrono::seconds(3))
        .setCooldownBackoff(2);

    auto validateResult = [](const auto& uidToLoginMap) {
        return uidToLoginMap.valid();
    };

    try {
        auto uidToLoginMap = maps::common::retry(
                [&]()->blackbox_client::UidToLoginMap {
                    return cfg_.blackboxClient()->uidToLoginMap(userIds);
                },
                retryPolicy,
                validateResult
        );

        return uidToLoginMap;
    } catch (std::exception& e) {
        ERROR() << "Blackbox error: " << e.what();
        return {};
    }
}

void RegularUploader::saveFeatureToYtTable(
    NYT::TTableWriterPtr<Node> writer,
    const db::Feature& feature,
    const std::unordered_map<blackbox_client::Uid, std::string>& userLogins
    ) const
{
    const std::string url = cfg_.mds()->makeReadUrl(feature.mdsKey());
    const auto userId = feature.userId().value_or("");

    std::string userLogin = "";
    if (isNumber(userId)) {
        auto userIdBlackbox = boost::lexical_cast<blackbox_client::Uid>(userId);
        if (userLogins.count(userIdBlackbox)) {
            userLogin = userLogins.at(userIdBlackbox);
        }
    }

    writer->AddRow(
        Node::CreateMap()
            ("id"         , yt::serialize(feature.id()))
            ("user_id"    , yt::serialize(userId))
            ("graph"      , yt::serialize(feature.graph()))
            ("login"      , yt::serialize(userLogin))
            ("timestamp"  , yt::serialize(feature.timestamp()))
            ("position"   , yt::serialize(feature.geodeticPos()))
            ("heading"    , yt::serialize(geolib3::normalize(feature.heading())))
            ("source_id"  , yt::serialize(feature.sourceId()))
            ("image_url"  , yt::serialize(url))
            ("orientation", yt::serialize(feature.orientation()))
            ("dataset"    , yt::serialize(feature.dataset()))
    );
}

uint64_t RegularUploader::loadAndWriteFeature(
    pgpool3::TransactionHandle& txn,
    NYT::TTableWriterPtr<Node> writer
    ) const
{
    uint64_t totalLoadedFeatures = 0;
    sql_chemistry::BatchLoad<db::table::Feature> batch{
        cfg_.uploadBatchSize(),
        db::table::Feature::isPublished
            && db::table::Feature::privacy != db::FeaturePrivacy::Secret
    };

    while (batch.next(*txn)) {
        auto userLogins = getUserLogins(batch);
        for (const auto& photoData : batch) {
            totalLoadedFeatures++;
            saveFeatureToYtTable(writer, photoData, userLogins);
        }
    }

    return totalLoadedFeatures;
}

uint64_t RegularUploader::loadAndSaveFromDatabase() const
{
    NYT::ITransactionPtr ytTxn = cfg_.ytClient()->StartTransaction();
    ytTxn->Create(cfg_.featuresTablePath(), NYT::NT_TABLE, NYT::TCreateOptions().Recursive(true).Force(true));
    const auto writer = ytTxn->CreateTableWriter<Node>(cfg_.featuresTablePath());

    auto slaveTxn = cfg_.pool()->slaveTransaction();
    uint64_t totalLoadedFeatures = loadAndWriteFeature(slaveTxn, writer);

    writer->Finish();
    ytTxn->Commit();

    return totalLoadedFeatures;
}

} // namespace maps::mrc::regular_upload
