#include <util/draft/date.h>

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

#include <wmconsole/version3/searchqueries-mr/conf/yt.h>
#include <wmconsole/version3/processors/acceptance/conf/config.h>
#include <wmconsole/version3/processors/acceptance/protos/acceptance.pb.h>
#include <wmconsole/version3/wmcutil/log.h>

#include "task_traffic.h"

namespace NWebmaster {
namespace NAcceptance {

using namespace NJupiter;

TInputTag<NProto::TAntiallThreatSrc> AntiallThreatSrcInputTag(0);
TInputTag<NProto::TAntiallThreat> AntiallThreatInputTag(1);

TOutputTag<NProto::TAntiallThreat> AntiallThreatOutputTag(0);
TOutputTag<NProto::TAntiallThreat> AntiallThreatPreviousOutputTag(1);

struct TPrepareDataMapper : public TTaggedMapper {
    void DoTagged(TTagedReader reader, TTagedWriter writer) final {

        NProto::TAntiallThreat dstMsg;
        for (; reader.IsValid(); reader.Next()) {
            if (reader.IsCurrentTable(AntiallThreatSrcInputTag)) {
                const NProto::TAntiallThreatSrc &srcMsg = reader.GetRow(AntiallThreatSrcInputTag);
                dstMsg.SetHost(srcMsg.GetHost());
                dstMsg.SetThreat(srcMsg.GetThreat());
                dstMsg.SetFromPrevSnapshot(false);
            } else {
                dstMsg = reader.GetRow(AntiallThreatInputTag);
                dstMsg.SetFromPrevSnapshot(true);
            }
            writer.AddRow(dstMsg, AntiallThreatOutputTag);
        }
    }
};

REGISTER_MAPPER(TPrepareDataMapper)

struct TSymmetricDifferenceSnapshotsReducer : public TTaggedReducer {
    void DoTagged(TTagedReader reader, TTagedWriter writer) final {
        THashSet<TString> currentThreatsSet;
        THashSet<TString> previousThreatSet;

        NProto::TAntiallThreat dstMsg;
        dstMsg.SetHost(reader.GetRow(AntiallThreatInputTag).GetHost());

        for (; reader.IsValid(); reader.Next()) {
            const NProto::TAntiallThreat &row = reader.GetRow(AntiallThreatInputTag);
            if (row.GetFromPrevSnapshot()) {
                previousThreatSet.insert(row.GetThreat());
            } else {
                currentThreatsSet.insert(row.GetThreat());
                writer.AddRow(row, AntiallThreatPreviousOutputTag);
            }
        }

        //берем симметрическую разность прошлого snapshot и нового
        for (auto &threat: currentThreatsSet) {
            if (previousThreatSet.erase(threat) > 0) {
                continue;
            }
            dstMsg.SetThreat(threat);
            dstMsg.SetFromPrevSnapshot(false);
            writer.AddRow(dstMsg, AntiallThreatOutputTag);
        }
        for (auto &threat: previousThreatSet) {
            dstMsg.SetThreat(threat);
            dstMsg.SetFromPrevSnapshot(true);
            writer.AddRow(dstMsg, AntiallThreatOutputTag);
        }
    }
};

REGISTER_REDUCER(TSymmetricDifferenceSnapshotsReducer)

void SynchronizeAntiTablesAndMakeDiff(const NYT::IClientPtr &client,
                                      const TString &tableSrcName,
                                      const TString &tablePrevName,
                                      const TString &tableDiffName,
                                      const TString &tableStatisticsName,
                                      const double changesShareThreshold) {
    NYT::ITransactionPtr tx = client->StartTransaction();
    TString timestampSrc = NYTUtils::GetAttr(tx, tableSrcName, TConfig::CInstance().ATTR_MODIFICATION_TIME).AsString();

    TTable<NProto::TAntiallThreat> tablePrev(tx, tablePrevName);
    tablePrev.PreCreate();

    TString timestampDest = NYTUtils::GetAttrOrDefault<TString>(tx, tablePrevName, TConfig::CInstance().ATTR_UPDATE_TIME, "");
    bool forceAcceptCurrentAcceptance = NYTUtils::GetAttrOrDefault<bool>(tx, tablePrevName, TConfig::CInstance().ATTR_FORCE_ACCEPT, false);
    if (timestampSrc == timestampDest) {
        tx->Abort();
        return;
    }

    TTable<NProto::TAntiallThreatSrc> tableSrc(tx, tableSrcName);
    TTable<NProto::TAntiallThreat> tableDiff(tx, tableDiffName);


    TMapReduceCmd<TPrepareDataMapper, TSymmetricDifferenceSnapshotsReducer>
            (tx)
            .Input(tableSrc, AntiallThreatSrcInputTag)
            .Input(tablePrev, AntiallThreatInputTag)
            .IntermediateMapTag(AntiallThreatOutputTag)
            .IntermediateReduceTag(AntiallThreatInputTag)
            .Output(tableDiff, AntiallThreatOutputTag)
            .Output(tablePrev, AntiallThreatPreviousOutputTag)
            .ReduceBy({"Host"})
            .SortBy({"Host"})
            .Do();


    i64 fullHostCount = tableSrc.GetRecordsCount();
    i64 diffHostCount = tableDiff.GetRecordsCount();
    double changesShare = (double) diffHostCount / fullHostCount;

    if (!forceAcceptCurrentAcceptance && changesShare > changesShareThreshold) {
        LOG_ERROR("Source: %s", tableSrcName.c_str());
        LOG_ERROR("Previous: %s", tablePrevName.c_str());
        LOG_ERROR("Diff: %s", tableDiffName.c_str());
        LOG_ERROR("Statistics: %s", tableStatisticsName.c_str());
        LOG_ERROR("Hosts count: %ld", fullHostCount);
        LOG_ERROR("Diff size: %ld", diffHostCount);
        LOG_ERROR("changesShare(%f) over a given threshold(%f)",
                  changesShare, changesShareThreshold
        );

        TMap<bool, TMap<TString, int>> stats;
        auto reader = tableDiff.GetReader();
        for (; reader->IsValid(); reader->Next()) {
            stats[reader->GetRow().GetFromPrevSnapshot()][reader->GetRow().GetThreat()]++;
        }

        for (const auto &prevObj : stats) {
            for (const auto &threatObj : prevObj.second) {
                const bool gone = prevObj.first;
                LOG_ERROR("  %s%7d = %s", gone ? "-" : "+", threatObj.second, threatObj.first.c_str());
            }
        }

        ythrow yexception() << "changes were more than a threshold in antiall acceptance";
    }

    NYTUtils::SetAttr(tx, tablePrevName, TConfig::CInstance().ATTR_FORCE_ACCEPT, false);
    NYTUtils::SetAttr(tx, tablePrevName, TConfig::CInstance().ATTR_UPDATE_TIME, timestampSrc);
    NYTUtils::SetAttr(tx, tableDiffName, TConfig::CInstance().ATTR_FULL_HOST_COUNT, fullHostCount);
    NYTUtils::SetAttr(tx, tableDiffName, TConfig::CInstance().ATTR_DIFF_HOST_COUNT, diffHostCount);
    NYTUtils::SetAttr(tx, tableDiffName, TConfig::CInstance().ATTR_CHANGES_SHARE, changesShare);

    auto statisticsTable = TTable<NProto::TStatisticsAnti>(tx, tableStatisticsName).ForAppend(true).GetWriter();

    NProto::TStatisticsAnti statisticsRow;
    statisticsRow.SetFullHostCount(fullHostCount);
    statisticsRow.SetDiffHostCount(diffHostCount);
    statisticsRow.SetChangesShare(changesShare);
    statisticsRow.SetTimestamp(timestampSrc);

    statisticsTable->AddRow(statisticsRow);
    statisticsTable->Finish();
    TSortCmd<NProto::TStatisticsAnti>(tx, TTable<NProto::TStatisticsAnti>(tx, tableStatisticsName))
            .By({"Timestamp"})
            .OperationWeight(TConfig::CInstance().OPERATION_WEIGHT)
            .Do()
            ;
    tx->Commit();
}

int AcceptAntiall(int, const char **) {
    NYT::IClientPtr client = NYT::CreateClient(TConfig::CInstance().MR_SERVER_HOST_ANTIALL);

    SynchronizeAntiTablesAndMakeDiff(client,
                                     TConfig::CInstance().TABLE_SRC_ANTISPAM,
                                     TConfig::CInstance().TABLE_ACCEPTANCE_ANTI_ANTISPAM_SNAPSHOT_PREV,
                                     TConfig::CInstance().TABLE_ACCEPTANCE_ANTI_ANTISPAM_DIFF,
                                     TConfig::CInstance().TABLE_ACCEPTANCE_ANTI_ANTISPAM_STATISTICS,
                                     TConfig::CInstance().ANTISPAM_THRESHOLD);

    SynchronizeAntiTablesAndMakeDiff(client,
                                     TConfig::CInstance().TABLE_SRC_ANTIVIR,
                                     TConfig::CInstance().TABLE_ACCEPTANCE_ANTI_ANTIVIR_SNAPSHOT_PREV,
                                     TConfig::CInstance().TABLE_ACCEPTANCE_ANTI_ANTIVIR_DIFF,
                                     TConfig::CInstance().TABLE_ACCEPTANCE_ANTI_ANTIVIR_STATISTICS,
                                     TConfig::CInstance().ANTIVIR_THRESHOLD);
    return 0;
}

}
} //namespace NWebmaster
