#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/searchqueries-mr/protos/user_sessions.pb.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 {

const char *FORMAT = "%Y-%m-%d";

using namespace NJupiter;

struct TTopHostsReducer : public NYT::IReducer<NYT::TTableReader<NUserSessions::NProto::TStatistics>, NYT::TTableWriter<NProto::TTopHost>> {
    void Do(TReader *input, TWriter *output) override {
        long clicks = 0;
        NProto::TTopHost dstMsg;
        dstMsg.SetHost(input->GetRow().GetHost());
        for (; input->IsValid(); input->Next()) {
            clicks += input->GetRow().GetClicks();
        }
        if (clicks > 0) {
            dstMsg.SetClicks(clicks);
            output->AddRow(dstMsg);
        }
    }
};

REGISTER_REDUCER(TTopHostsReducer)

static unsigned GetWeekDayTZ(time_t ts) {
    struct tm *ti = localtime(&ts);
    return ti->tm_wday;
}

static TString GetStartOfWeekTZ(time_t ts = Now().TimeT()) {
    const static int WEEK_START_OFFSET_DAYS = 6;
    const int weekDay = (GetWeekDayTZ(ts) + WEEK_START_OFFSET_DAYS) % 7;
    const int startOfWeek = TDate(ts).GetStart() - weekDay * 86400;
    return TDate(startOfWeek).ToStroka("%Y-%m-%d");
}

void LoadTopHosts(NYT::IClientBasePtr client, THashSet<TString> &topHosts, size_t top) {
    TMultiMap<int, TString> topHostsMap;
    auto reader = TTable<NProto::TTopHost>(client, TConfig::CInstance().TABLE_ACCEPTANCE_TOP_HOSTS).GetReader();
    for (; reader->IsValid(); reader->Next()) {
        topHostsMap.emplace(-reader->GetRow().GetClicks(), reader->GetRow().GetHost());
    }

    for (const auto &obj : topHostsMap) {
        if (topHosts.size() < top) {
            topHosts.insert(obj.second);
        } else {
            break;
        }
    }

    LOG_INFO("acceptance.traffic, loaded %lu top hosts", topHosts.size());
}

int UpdateTopHosts(int, const char **) {
    NYT::IClientPtr client = NYT::CreateClient(TCommonYTConfigSQ::CInstance().MR_SERVER_HOST_USER_SESSIONS);

    const TString outputTable = TConfig::CInstance().TABLE_ACCEPTANCE_TOP_HOSTS;
    const TString updateTime = GetStartOfWeekTZ();
    if (client->Exists(outputTable) && NYTUtils::GetAttr(client, outputTable, TConfig::CInstance().ATTR_UPDATE_TIME).AsString() == updateTime) {
        LOG_INFO("acceptance.traffic, table %s is already processed", outputTable.c_str());
        return 0;
    }

    NYT::ITransactionPtr tx = client->StartTransaction();
    TDeque<TTable<NUserSessions::NProto::TStatistics>> inputTables;
    const TDate startDate = TDate(Now().TimeT());
    const TDate endDate = startDate - 180;
    for (TDate curDate = startDate; curDate > endDate && inputTables.size() < 28; --curDate) {
        const TString inputTable = NYTUtils::JoinPath(
            TCommonYTConfigSQ::CInstance().TABLE_PARSED_USER_SESSIONS_STATS_DAILY_ROOT,
            curDate.ToStroka(FORMAT)
        );

        if (tx->Exists(inputTable)) {
            inputTables.emplace_back(tx, inputTable);
        }
    }

    Y_ENSURE(inputTables.size() == 28, "Input tables set is incomple");

    TReduceCmd<TTopHostsReducer>(tx)
        .Inputs(inputTables)
        .Output(TTable<NProto::TTopHost>(tx, outputTable).AsSortedOutput({"Host"}))
        .ReduceBy({"Host"})
        .OperationWeight(TConfig::CInstance().OPERATION_WEIGHT)
        .Do()
    ;

    TSortCmd<NProto::TTopHost>(tx, TTable<NProto::TTopHost>(tx, outputTable))
        .By({"Host"})
        .OperationWeight(TConfig::CInstance().OPERATION_WEIGHT)
        .Do()
    ;

    NYTUtils::SetAttr(tx, outputTable, TConfig::CInstance().ATTR_UPDATE_TIME, updateTime);
    tx->Commit();

    return 0;
}

}
} //namespace NWebmaster
