#include "task_parse_daily.h"

#include "convert.h"
#include "parse.h"
#include "robots.h"
#include "table_config.h"
#include "utils.h"

#include <wmconsole/version3/processors/user_sessions/conf/config.h>
#include <wmconsole/version3/processors/user_sessions/library/common_parser_opts.h>
#include <wmconsole/version3/processors/user_sessions/protos/user_sessions.pb.h>

#include <wmconsole/version3/wmcutil/args.h>
#include <wmconsole/version3/wmcutil/log.h>

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

#include <library/cpp/compute_graph/compute_graph.h>
#include <library/cpp/getopt/last_getopt.h>

namespace NWebmaster {
namespace NUserSessions {

void ParseDailyUserSessions(NYT::IClientBasePtr client, const TBlockStatInfo &bsi, const TString &dateStr, bool force, bool columnarUserSessions) {
    TTableConfig ttcfg(dateStr, TTableConfig::E_INPUT_DAILY, columnarUserSessions, /* enableIncomplete */ true);
    TMetrikaRobotHits robotHits;
    LoadMetrikaRobots(ttcfg, robotHits);
    int fraudTypeMask = 0;
    for (auto fraudType : {EUserFraudType::Clean, EUserFraudType::Frauds, EUserFraudType::Robots}) {
        fraudTypeMask = SetUserFraudTypeInMask(fraudTypeMask, fraudType);
    }
    ParseUserSessions(client, bsi, ttcfg, robotHits, fraudTypeMask, force);
    ConvertUserSessionsToQueriesV4(client, ttcfg, force);
    LOG_INFO("user_sessions daily, parsing %s - done", dateStr.c_str());
}

void UpdateDailyUserSessions(NYT::IClientPtr client, const TBlockStatInfo &bsi, size_t periodDays, int retries, bool failOnError, const TString& latestDate) {
    Y_ENSURE_BT(periodDays <= 31, "Sanity check for number of days to parse during a single run");
    const TDate startDate = (
        latestDate.empty()
            ? TDate(GetLatestUserSessionsTimestamp())
            : TDate(latestDate)
    );
    const TDate endDate = startDate - periodDays;
    //const TDate startDate = TDate("2021-05-19", TConfig::FORMAT);
    //const TDate endDate = TDate("2021-05-18", TConfig::FORMAT);

    const TDate switchToColumnarUserSessions = TDate("2021-08-31", TConfig::FORMAT);
    LOG_INFO("Processing dates from %s exclusive to %s inclusive", endDate.ToStroka().data(), startDate.ToStroka().data());
    NComputeGraph::TJobRunner runner(4);
    for (TDate curDate = startDate; curDate > endDate; --curDate) {
        const TString dateStr = curDate.ToStroka(TConfig::FORMAT);
        const bool columnarUserSessions = curDate >= switchToColumnarUserSessions;
        const TString root = NJupiter::JoinYtPath(TCommonYTConfigSQ::CInstance().TABLE_SOURCE_USER_SESSIONS_DAILY_ROOT, dateStr);
        if (!client->Exists(root)) {
            ythrow yexception() << "user_sessions daily, there is no expected root: " << root;
        }

        runner.AddJob([=, &bsi, &client]() {
            for (int retry = 0; retry < retries; retry++) {
                try {
                    ParseDailyUserSessions(client, bsi, dateStr, /* force */ true, columnarUserSessions);
                    break;
                } catch(yexception &e) {
                    LOG_ERROR("user_sessions daily, unable to parse %s: %s", dateStr.c_str(), e.what());
                    if (retry + 1 < retries) {
                        LOG_ERROR("Waiting 5 minutes to retry");
                        Sleep(TDuration::Minutes(5));
                    } else if (failOnError) {
                        throw;
                    }
                }
            }
        });
    }
    try {
        runner.Run();
        LOG_INFO("All jobs have finished");
    } catch (yexception& e) {
        LOG_ERROR("Caught exception by parse jobs: %s", e.what());
        if (failOnError) {
            throw e;
        } else {
            LOG_INFO("Ignoring the exception");
        }
    }
}

static const TString GetFullServerName(const TString& serverName) {
    if (serverName.find('.') == TString::npos) {
        return serverName + ".yt.yandex.net";
    }
    return serverName;
}

int TaskParseDaily(int argc, const char **argv) {
    NJupiter::TCmdParams params;

    TCommonParserOpts commonParserOpts;
    TCommonParserOptsParser(params, commonParserOpts)
        .AddEnvRoot()
        .AddLogPath()
        .AddYtProxy()
        .AddYtPrefix()
        .AddEnableStderrLog();

    size_t periodDays;
    int retries;
    bool failOnError;
    TString date;
    params.AddOptional("period-days", "Number of days to parse", "<number>", "6", &periodDays);
    params.AddOptional("retries", "Number of retries to launch parsing", "<number>", "5", &retries);
    params.AddOptionalFlag("fail-on-error", "Do not suppress exceptions after retries are exhausted", &failOnError);
    params.AddOptional("date", "Latest date to parse", "<YYYYMMDD>", &date);

    params.Parse(argc, argv);

    TArgs::Instance().LogPath = commonParserOpts.LogPath;
    TCustomYTEnvironment::Instance().Init(commonParserOpts.EnvRoot);
    TLogger::Instance();
    if (!commonParserOpts.EnableStderrLog) {
        NYTUtils::DisableLogger();
    }

    Y_ENSURE_BT(commonParserOpts.YtProxy.empty() == commonParserOpts.YtPrefix.empty(), "Either both or none must be specified");
    if (!commonParserOpts.YtProxy.empty()) {
        const TString ytProxy = GetFullServerName(commonParserOpts.YtProxy);
        auto& config = TConfig::Instance();
        config.MR_SERVER_HOST_USER_SESSIONS = ytProxy;
        config.MR_SERVER_HOST_HITLOG = ytProxy;
        TCommonYTConfigSQ::Instance() = TCommonYTConfigSQ(
            ytProxy,
            commonParserOpts.YtPrefix,
            NJupiter::JoinYtPath(commonParserOpts.YtPrefix, "webmaster", commonParserOpts.EnvRoot)
        );
    }
    NYT::IClientPtr client = NYT::CreateClient(TConfig::CInstance().MR_SERVER_HOST_USER_SESSIONS);

    PrepareDaily(client, TCommonYTConfigSQ::CInstance().TABLE_PARSED_USER_SESSIONS_DAILY_ROOT);
    PrepareDaily(client, TCommonYTConfigSQ::CInstance().TABLE_PARSED_USER_SESSIONS_PRSLOG_DAILY_ROOT);
    PrepareDaily(client, TCommonYTConfigSQ::CInstance().TABLE_PARSED_USER_SESSIONS_STATS_DAILY_ROOT);
    PrepareDaily(client, TCommonYTConfigSQ::CInstance().TABLE_PARSED_USER_SESSIONS_ROBOTS_METRIKA_ROOT);
    PrepareDaily(client, TCommonYTConfigSQ::CInstance().TABLE_CONVERTED_QUERIES_V4_ROOT, true /*legacy*/);

    const TBlockStatInfo bsi = GetBlockstatInfo(client);
    UpdateDailyUserSessions(client, bsi, periodDays, retries, failOnError, date);
    return 0;
}

} //namespace NUserSessions
} //namespace NWebmaster
