#pragma once

#include <mapreduce/yt/interface/client.h>
#include <mapreduce/yt/interface/protos/yamr.pb.h>

#include <wmconsole/version3/processors/indexing/library/sitetree.h>
#include <wmconsole/version3/processors/indexing/sitetree/protos/searchbase.pb.h>
#include <wmconsole/version3/protos/exported.pb.h>

namespace NWebmaster {

struct TMonsterUrlRecord {
    struct TSearchData {
        size_t DocsOnSearch = 0;
        size_t DocsSearchDiffNew = 0;
        size_t DocsSearchDiffGone = 0;
    };

    const size_t MAX_URL_SAMPLES = TWrapperNode::TOTAL_URL_SAMPLE_ALLOWED; /* sitetree.h */

public:
    TMonsterUrlRecord() = default;

    TMonsterUrlRecord(proto::urltree::NodeInfo &msg) {
        Docs = msg.num_of_docs();

        TSearchData *searchData = &Production;
        if (msg.search_source_id() == proto::urltree::ACCEPTANCE) {
            searchData = &Acceptance;
        }

        searchData->DocsOnSearch = msg.num_of_docs_on_search();
        searchData->DocsSearchDiffNew = msg.num_of_new_search_docs();
        searchData->DocsSearchDiffGone = msg.num_of_gone_search_docs();

        for (int i = 0; i < msg.turbo_source_info_size(); i++) {
            const proto::urltree::TurboSourceInfo &info = msg.turbo_source_info(i);
            TurboSources[info.source_id()] = info.total();
        }

        for (int i = 0; i < msg.httpcodes_size(); i++) {
            const proto::urltree::HttpCodeInfo &info = msg.httpcodes(i);
            HttpCodes[info.code()] = info.total();
        }

        for (int i = 0; i < msg.url_statuses_size(); i++) {
            const proto::urltree::UrlStatusInfo &info = msg.url_statuses(i);
            ExcludedUrlStatuses[info.url_status()] += info.total();
        }
    }

    TMonsterUrlRecord& operator+=(const TMonsterUrlRecord& that) {
        Docs += that.Docs;

        Production.DocsOnSearch += that.Production.DocsOnSearch;
        Production.DocsSearchDiffNew += that.Production.DocsSearchDiffNew;
        Production.DocsSearchDiffGone += that.Production.DocsSearchDiffGone;

        Acceptance.DocsOnSearch += that.Acceptance.DocsOnSearch;
        Acceptance.DocsSearchDiffNew += that.Acceptance.DocsSearchDiffNew;
        Acceptance.DocsSearchDiffGone += that.Acceptance.DocsSearchDiffGone;

        for (const auto &obj : that.TurboSources) {
            TurboSources[obj.first] += obj.second;
        }

        for (const auto &obj : that.HttpCodes) {
            HttpCodes[obj.first] += obj.second;
        }

        for (const auto &obj : that.ExcludedUrlStatuses) {
            ExcludedUrlStatuses[obj.first] += obj.second;
        }

        return *this;
    }

    void SaveToNodeInfo(proto::urltree::SearchSourceId sourceId, proto::urltree::YandexSearchShardId shardId, proto::urltree::NodeInfo &node) const {
        const TSearchData *searchData = &Production;
        if (sourceId == proto::urltree::ACCEPTANCE) {
            searchData = &Acceptance;
        }

        node.set_search_source_id(sourceId);
        node.set_shard_id(shardId);
        node.set_name("/");
        node.set_num_of_docs(Docs);
        node.set_num_of_docs_on_search(searchData->DocsOnSearch);
        node.set_num_of_new_search_docs(searchData->DocsSearchDiffNew);
        node.set_num_of_gone_search_docs(searchData->DocsSearchDiffGone);
        node.set_num_of_doubles(0);
        node.set_node_id(1);
        node.set_parent_id(0);

        for (const auto &obj : TurboSources) {
            proto::urltree::TurboSourceInfo *info = node.add_turbo_source_info();
            info->set_source_id(obj.first);
            info->set_total(obj.second);
        }

        for (const auto &obj : HttpCodes) {
            int code = obj.first;
            size_t count = obj.second;
            proto::urltree::HttpCodeInfo *info = node.add_httpcodes();
            info->set_code(code);
            info->set_total(count);
        }

        for (const auto &obj : ExcludedUrlStatuses) {
            int urlStatus = obj.first;
            size_t count = obj.second;
            proto::urltree::UrlStatusInfo *info = node.add_url_statuses();
            info->set_url_status(urlStatus);
            info->set_total(count);
        }
    }

    void AddTurbo(THashMap<size_t, size_t> &turboSource, size_t turboSourceFlagss){
        if (turboSourceFlagss & (turboSourceFlagss - 1UL)  == 0) {
            turboSource[turboSourceFlagss]++;
        } else {
            int sourceId = 1;
            while (turboSourceFlagss){
                if (turboSourceFlagss & 1UL){
                    turboSource[sourceId]++;
                }
                sourceId <<= 1;
                turboSourceFlagss >>= 1;
            }
        }
    }

    void Update(const proto::urltree::RecordSourceInfo &msg) {
        if (msg.has_search_base_acceptance() && msg.search_base_acceptance().from_jupiter()) {
            Acceptance.DocsOnSearch++;
            Production.DocsOnSearch++;
            if (msg.turbo_source_flags()) {
                AddTurbo(TurboSources, msg.turbo_source_flags());
            }
        }

        if (msg.has_search_base_acceptance()) {
            if (msg.search_base_acceptance().search_diff_status() == proto::urltree::SEARCH_DIFF_STATUS_NEW) {
                Acceptance.DocsSearchDiffNew++;
                Production.DocsSearchDiffNew++;
            } else if (msg.search_base_acceptance().search_diff_status() == proto::urltree::SEARCH_DIFF_STATUS_GONE) {
                Acceptance.DocsSearchDiffGone++;
                Production.DocsSearchDiffGone++;
            }
        }

        if (msg.has_latest_url_info()) {
            Docs++;
            if (msg.latest_url_info().has_http_code()) {
                HttpCodes[msg.latest_url_info().http_code()]++;
            }

            if (msg.latest_url_info().has_jupiter_url_status_excluded()) {
                ExcludedUrlStatuses[msg.latest_url_info().jupiter_url_status_excluded()]++;
            }
        }
    }

public:
    size_t Docs = 0;
    TSearchData Production;
    TSearchData Acceptance;
    THashMap<size_t, size_t> TurboSources;
    THashMap<int, size_t> HttpCodes;
    THashMap<int, size_t> ExcludedUrlStatuses;
};

struct TMapMonsterSitetree : public NYT::IMapper<NYT::TTableReader<NProto::TPreparedUrl>, NYT::TTableWriter<NYT::TYamr>> {
public:
    void Do(TReader *input, TWriter *output) override {
        THashMap<TString, TMonsterUrlRecord> hostsData;
        for (; input->IsValid(); input->Next()) {
            const auto &row = input->GetRow();
            proto::urltree::RecordSourceInfo msg;
            Y_PROTOBUF_SUPPRESS_NODISCARD msg.ParseFromString(row.GetProto());
            hostsData[row.GetHost()].Update(msg);
        }

        NYT::TYamr dstMsg;
        for (const auto &obj : hostsData) {
            TString stream;
            const TString &host = obj.first;
            const TMonsterUrlRecord &record = obj.second;
            proto::urltree::NodeInfo msg;

            record.SaveToNodeInfo(proto::urltree::PRODUCTION, proto::urltree::RU, msg);
            Y_PROTOBUF_SUPPRESS_NODISCARD msg.SerializeToString(&stream);

            dstMsg.SetKey(host);
            dstMsg.SetValue(stream);
            output->AddRow(dstMsg);

            record.SaveToNodeInfo(proto::urltree::ACCEPTANCE, proto::urltree::RU_ACCEPTANCE, msg);
            Y_PROTOBUF_SUPPRESS_NODISCARD msg.SerializeToString(&stream);
            dstMsg.SetValue(stream);
            output->AddRow(dstMsg);
        }
    }
};

struct TReduceMonsterSitetree : public NYT::IReducer<NYT::TTableReader<NYT::TYamr>, NYT::TTableWriter<NYT::TYamr>> {
    Y_SAVELOAD_JOB(ProductionTimestamp, AcceptanceTimestamp)

public:
    TReduceMonsterSitetree() = default;
    TReduceMonsterSitetree(time_t productionTimestamp, time_t acceptanceTimestamp)
        : ProductionTimestamp(productionTimestamp)
        , AcceptanceTimestamp(acceptanceTimestamp)
    {
    }

    void Do(TReader *input, TWriter *output) override {
        const TString host = input->GetRow().GetKey();
        THashMap<proto::urltree::YandexSearchShardId, TMonsterUrlRecord> records;

        for (; input->IsValid(); input->Next()) {
            proto::urltree::NodeInfo msg;
            Y_PROTOBUF_SUPPRESS_NODISCARD msg.ParseFromString(input->GetRow().GetValue());
            records[msg.shard_id()] += TMonsterUrlRecord(msg);
        }

        TString stream;
        proto::urltree::HostInfo msg;
        msg.set_hostname(host);
        msg.set_fake(false);
        msg.set_searchdb_production_timestamp(ProductionTimestamp);
        msg.set_searchdb_acceptance_timestamp(AcceptanceTimestamp);
        records.at(proto::urltree::RU).SaveToNodeInfo(proto::urltree::PRODUCTION, proto::urltree::RU, *msg.add_nodes());
        records.at(proto::urltree::RU_ACCEPTANCE).SaveToNodeInfo(proto::urltree::ACCEPTANCE, proto::urltree::RU_ACCEPTANCE, *msg.add_nodes());
        Y_PROTOBUF_SUPPRESS_NODISCARD msg.SerializeToString(&stream);

        NYT::TYamr dstMsg;
        dstMsg.SetKey(host);
        dstMsg.SetSubkey("");
        dstMsg.SetValue(stream);
        output->AddRow(dstMsg);
    }

public:
    time_t ProductionTimestamp = 0;
    time_t AcceptanceTimestamp = 0;
};

} //namespace NWebmaster
