#include <limits>

#include <util/charset/wide.h>
#include <util/string/escape.h>

#include <robot/jupiter/protos/acceptance.pb.h>
#include <robot/library/yt/static/table.h>

#include <wmconsole/version3/library/jupiter/jupiter.h>
#include <wmconsole/version3/processors/indexing/important_urls/conf/config.h>
#include <wmconsole/version3/wmcutil/hostid.h>
#include <wmconsole/version3/wmcutil/log.h>
#include <wmconsole/version3/wmcutil/string.h>
#include <wmconsole/version3/wmcutil/url.h>
#include <wmconsole/version3/wmcutil/yt/yt_runner.h>
#include <wmconsole/version3/wmcutil/yt/yt_utils.h>

#include "schemes.h"
#include "task_merge.h"

namespace NWebmaster {
namespace NImportantUrls {

class TMapImportantTableUpdates : public NYT::IMapper<NYT::TTableReader<NYT::TNode>, NYT::TTableWriter<NYT::TNode>> {
public:
    Y_SAVELOAD_JOB(TablesByIndexes)

    TMapImportantTableUpdates() = default;

    TMapImportantTableUpdates(const THashMap<ui32, ui32> &tablesByIndexes)
            : TablesByIndexes(tablesByIndexes) {
    }

    void Do(TReader *reader, TWriter *writer) override {
        for (; reader->IsValid(); reader->Next()) {
            NYT::TNode row = reader->GetRow();
            writer->AddRow(row(F_TABLE_TIMESTAMP, TablesByIndexes[reader->GetTableIndex()]));
        }
    }
public:
    THashMap<ui32, ui32> TablesByIndexes;
};

REGISTER_MAPPER(TMapImportantTableUpdates)

int TaskMerge(int, const char **) {
    LOG_INFO("Running TaskMerge!");
    const TConfig &config = TConfig::CInstance();

    NYT::IClientPtr client = NYT::CreateClient(config.MR_SERVER_HOST);
    NYT::ITransactionPtr tx = client->StartTransaction();

    NYT::TTableSchema preparedUrlsSchema = CreatePreparedUrlsSchema();
    NYT::TTableSchema snapshotSchema = CreateSnapshotSchema(tx);
    NYT::TTableSchema updateSchema = CreateUpdateSchema(snapshotSchema);

    TDeque<NYTUtils::TTableInfo> updateTables, tablesForMerge;
    TDeque<NYTUtils::TResolvedNode> dest;
    NYTUtils::GetTableList(tx, config.TABLE_IMPORTANT_URLS_UPDATES, updateTables);

    // sort by name
    sort(updateTables.begin(), updateTables.end(), NYTUtils::TTableInfo::TNameLess());
    // find last with imported=true
    auto lastImported = find_if(updateTables.rbegin(), updateTables.rend(), [tx](const NYTUtils::TTableInfo &tbl) {
        return NYTUtils::GetAttrOrDefault<bool>(tx, tbl.Name, TAttrName::Imported, false);
    });
    updateTables.resize(std::min(static_cast<size_t>(distance(lastImported, updateTables.rend())), config.MAX_MERGED_TABLES));

    if (!updateTables.empty()) {
        TOpRunner mergeRunner(tx);
        NYT::TRichYPath mergedTempTable = NYT::TRichYPath(config.TABLE_IMPORTANT_URLS_MERGED + ".temp")
                .Schema(CreateMergeSchema(updateSchema));
        mergeRunner.OutputNode(mergedTempTable);

        NYT::TRichYPath mergedTable = NYT::TRichYPath(config.TABLE_IMPORTANT_URLS_MERGED)
                .Schema(CreateMergeSchema(updateSchema));

        THashMap<ui32, ui32> tablesByIndexes;
        for (const auto &tableInfo : updateTables) {
            bool suspicious = false;

            try {
                suspicious = NYTUtils::GetAttr(tx, tableInfo.Name, TAttrName::Suspicious).AsBool();
            } catch (yexception &) {
            }

            if (suspicious) {
                LOG_ERROR("important_urls, detected suspicous update table %s", tableInfo.Name.c_str());
                ythrow yexception() << "important_urls, detected suspicous update table " << tableInfo.Name;
            }

            ui32 tableTimestamp;
            const TString &tableName = NYTUtils::GetTableName(tableInfo.Name);
            if (TryFromString(tableName, tableTimestamp)) {
                mergeRunner.InputNode(tableInfo.Name);
                tablesByIndexes[tablesByIndexes.size()] = tableTimestamp;
                tablesForMerge.push_back(tableInfo);
            } else {
                LOG_ERROR("Unexpected table %s", tableName.data());
            }
        }

        mergeRunner
                .Comment("Mapping to single table")
                .Map(new TMapImportantTableUpdates(tablesByIndexes))

                .Comment("Sorting results")
                .SortBy(F_HOST, F_PATH, F_TABLE_TIMESTAMP)
                .Sort(mergedTempTable.Path_)

                .Comment("Merging results")
                .InputNode(mergedTempTable.Path_)
                .InputNode(mergedTable)
                .OutputNode(mergedTable)
                .MergeBy(F_HOST, F_PATH, F_TABLE_TIMESTAMP)
                .Merge()
                .Drop(mergedTempTable.Path_);
    }

    // remove old update tables
    for (auto &tableInfo : tablesForMerge) {
        tx->Remove(tableInfo.Name);
    }

    tx->Commit();

    return 0;
}

} //namespace NImportantUrls
} //namespace NWebmaster
