#include <util/generic/hash_set.h>
#include <util/generic/size_literals.h>

#include <robot/library/yt/static/command.h>

#include <wmconsole/version3/library/jupiter/jupiter.h>
#include <wmconsole/version3/wmcutil/hostid.h>
#include <wmconsole/version3/wmcutil/url.h>
#include <wmconsole/version3/wmcutil/yt/triggers.h>
#include <wmconsole/version3/processors/tools/IKS/utils/canonizer.h>
#include <wmconsole/version3/processors/tools/host2vec/utils/utils.h>
#include <wmconsole/version3/processors/user_sessions/niche/conf/config.h>
#include <wmconsole/version3/processors/user_sessions/niche/miner/tables.pb.h>

#include "task_source_rivals.h"

namespace NWebmaster {
namespace NNiche {

using namespace NJupiter;

//ReduceBy MainHost
struct TAddedRivalsMirrorsExtractReducer : public NYT::IReducer<NYT::TTableReader<NProto::TMirror>, NYT::TTableWriter<NProto::TMirror>> {
    Y_SAVELOAD_JOB(Owners)

    TAddedRivalsMirrorsExtractReducer() = default;
    TAddedRivalsMirrorsExtractReducer(const THashSet<TString> &owners)
        : Owners(owners)
    {
    }

    void Do(TReader *input, TWriter *output) override {
        NProto::TMirror dstMsg;
        for (; input->IsValid(); input->Next()) {
            const auto &row = input->GetRow();
            const TString owner = OwnerCanonizer.GetOwner(row.GetHost());

            if (Owners.contains(owner) && NUtils::IsSubdomain(row.GetMainHost(), owner)) {
                dstMsg.SetHost(owner);
                dstMsg.SetMainHost(row.GetMainHost());
                dstMsg.SetRank(row.GetRank());
                output->AddRow(dstMsg);
                break;
            }
        }
    }

public:
    THashSet<TString> Owners;
    TIKSCanonizer OwnerCanonizer;
};

REGISTER_REDUCER(TAddedRivalsMirrorsExtractReducer)

//ReduceBy MainHost
struct TAddedRivalsMirrorsEnrichMapper : public NYT::IMapper<NYT::TTableReader<NProto::TMirror>, NYT::TTableWriter<NProto::TAddedRival>> {
    Y_SAVELOAD_JOB(Rivals)

    TAddedRivalsMirrorsEnrichMapper() = default;
    TAddedRivalsMirrorsEnrichMapper(const THashMap<TString, THashSet<TString>> &rivals)
        : Rivals(rivals)
    {
    }

    void Do(TReader *input, TWriter *output) override {
        NProto::TAddedRival dstMsg;
        for (; input->IsValid(); input->Next()) {
            const auto &row = input->GetRow();

            TString mainDomain;
            if (!NHost2Vec::FixHost(row.GetMainHost(), mainDomain)) {
                continue;
            }

            if (!Rivals.contains(row.GetHost())) {
                continue;
            }
            for (const TString &host : Rivals.at(row.GetHost())) {
                dstMsg.SetHost(row.GetMainHost());
                dstMsg.SetRivalOwner(row.GetHost());
                dstMsg.SetMainHost(host);
                dstMsg.SetIsMainRivalHost(row.GetHost() == mainDomain);
                output->AddRow(dstMsg);
            }

        }
    }

public:
    THashMap<TString, THashSet<TString>> Rivals;
};

REGISTER_MAPPER(TAddedRivalsMirrorsEnrichMapper)

int TaskSourceAddedRivals(int, const char **) {
    const auto &cfg = TConfig::CInstance();
    NYT::IClientBasePtr client = NYT::CreateClient(cfg.MR_SERVER_HOST);

    TYtModificationTimeTrigger trigger(cfg.TABLE_NICHE_SOURCE_ADDED_RIVALS);
    if (!trigger.NeedUpdate(client, TAttrName::SrcModificationTime, cfg.TABLE_SOURCE_ADDED_RIVALS)) {
        LOG_INFO("miner, source, rivals source is already updated");
        return 0;
    }
    LOG_INFO("miner, source, added_rivals - starting");

    NYT::ITransactionPtr tx = client->StartTransaction();
    TTable<NProto::TMirror> intmMirrorsTable(tx, NYTUtils::JoinPath(cfg.TABLE_NICHE_TMP_ROOT, "rivals", "added-rivals-intm-mirrors"));
    TTable<NProto::TAddedRival> rivalsTable(tx, cfg.TABLE_NICHE_SOURCE_ADDED_RIVALS);

    THashMap<TString, THashSet<TString>> rivals;
    THashSet<TString> owners;
    TIKSCanonizer ownerCanonizer;

    for (auto reader = tx->CreateTableReader<NYT::TNode>(cfg.TABLE_SOURCE_ADDED_RIVALS); reader->IsValid(); reader->Next()) {
        const auto &row = reader->GetRow();
        const auto &rival = row["rival"].AsString();
        const auto host = TWebmasterHostId::FromHostId(row["host_id"].AsString()).ToHostName();
        const TString hostOwner = ownerCanonizer.GetOwner(host);
        const TString rivalOwner = ownerCanonizer.GetOwner(rival);
        rivals[rivalOwner].insert(host);
        rivals[hostOwner].insert(host);
        owners.insert(rivalOwner);
        owners.insert(hostOwner);
    }

    TReduceCmd<TAddedRivalsMirrorsExtractReducer>(tx, new TAddedRivalsMirrorsExtractReducer(owners))
        .Input(TTable<NProto::TMirror>(tx, GetJupiterMirrorsInProdTable(tx)))
        .Output(intmMirrorsTable)
        .ReduceBy({"MainHost"})
        .SortBy({"MainHost", "Rank"})
        .MemoryLimit(2_GBs)
        .Do()
    ;

    TMapCmd<TAddedRivalsMirrorsEnrichMapper>(tx, new TAddedRivalsMirrorsEnrichMapper(rivals))
        .Input(intmMirrorsTable)
        .Output(rivalsTable)
        .Do()
    ;

    TSortCmd<NProto::TAddedRival>(tx, rivalsTable)
        .By({"Host"})
        .Do()
    ;

    trigger.Update(tx, TAttrName::SrcModificationTime, cfg.TABLE_SOURCE_ADDED_RIVALS);
    //intmMirrorsTable.Drop();
    tx->Commit();
    LOG_INFO("miner, source, added_rivals - done");

    return 0;
}

} //namespace NNiche
} //namespace NWebmaster
