#include <library/cpp/string_utils/url/url.h>

#include <wmconsole/version3/wmcutil/hostid.h>
#include <wmconsole/version3/wmcutil/log.h>
#include <wmconsole/version3/wmcutil/regex.h>
#include <wmconsole/version3/wmcutil/yt/yt_runner.h>

#include "config.h"
#include "monitor.h"
#include "task_report.h"
#include "user_verifications.h"

namespace NWebmaster {

namespace {
const char *F_HOST                  = "Host";
const char *F_USER_ID               = "UserId";
const char *F_USER_LOGIN            = "UserLogin";
const char *F_VERIFICATION_TYPE     = "VerificationType";
const char *F_VERIFICATION_TIME     = "VerificationTime";
const char *F_TABLE_NO              = "TableNo";
}

struct TExtractUserVerificationsMapper : public NYT::IMapper<NYT::TTableReader<NYT::TNode>, NYT::TTableWriter<NYT::TNode>> {
public:
    void Do(TReader *input, TWriter *output) override {
        for (; input->IsValid(); input->Next()) {
            const NYT::TNode &row = input->GetRow();
            if (row["visible"].AsString() == "true") {
                output->AddRow(NYT::TNode()
                    (F_HOST, row["host_url"])
                    (F_USER_ID, FromString<long>(row["user_id"].AsString()))
                    (F_VERIFICATION_TIME, static_cast<time_t>(TInstant::ParseIso8601Deprecated(row["verification_date"].AsString()).Seconds()))
                    (F_VERIFICATION_TYPE, row["verification_type"].AsString())
                    (F_TABLE_NO, static_cast<ui32>(input->GetTableIndex()))
                );
            }
        }
    }
};

REGISTER_MAPPER(TExtractUserVerificationsMapper)

struct TExtractUserVerificationsReducer : public NYT::IReducer<NYT::TTableReader<NYT::TNode>, NYT::TTableWriter<NYT::TNode>> {
public:
    void Do(TReader *input, TWriter *output) override {
        const ui32 TABLENO_OLD_STATE = 0;
        const ui32 TABLENO_NEW_STATE = 1;

        struct TRecord {
            bool operator<(const TRecord &rhs) const {
                return UserId < rhs.UserId;
            }
        public:
            long UserId;
            time_t VerificationTime;
            TString VerificationType;
        };

        TSet<TRecord> oldVerifications, newVerifications;
        TDeque<TRecord> diffNewVerifications;

        const TString host = input->GetRow()[F_HOST].AsString();
        for (; input->IsValid(); input->Next()) {
            const NYT::TNode &row = input->GetRow();
            const ui32 tableNo = row[F_TABLE_NO].AsUint64();

            TRecord record;
            record.UserId = row[F_USER_ID].AsInt64();
            record.VerificationTime = row[F_VERIFICATION_TIME].AsInt64();
            record.VerificationType = row[F_VERIFICATION_TYPE].AsString();

            if (tableNo == TABLENO_OLD_STATE) {
                oldVerifications.insert(record);
            } else if (tableNo == TABLENO_NEW_STATE) {
                newVerifications.insert(record);
            } else {
                ythrow yexception() << "unknown table";
            }
        }

        std::set_difference(newVerifications.begin(), newVerifications.end(), oldVerifications.begin(), oldVerifications.end(), std::back_inserter(diffNewVerifications));
        for (const TRecord &record : diffNewVerifications) {
            output->AddRow(NYT::TNode()
                (F_HOST, host)
                (F_USER_ID, record.UserId)
                (F_VERIFICATION_TIME, record.VerificationTime)
                (F_VERIFICATION_TYPE, record.VerificationType)
            );
        }
    }
};

REGISTER_REDUCER(TExtractUserVerificationsReducer)

TString GetUserVerificationsTableName(const TDigestWeekConfig &weekConfig) {
    const auto &config = TConfig::CInstance();
    return NYTUtils::JoinPath(config.TABLE_DIGEST_SOURCE_USER_VERIFICATIONS, weekConfig.RangeName());
}

void PrepareUserVerifications(NYT::IClientBasePtr clientSearch, const TDigestWeekConfig &weekConfig) {
    const auto &config = TConfig::CInstance();
    const TString userVerificationsSourceTable = GetUserVerificationsTableName(weekConfig);
    if (clientSearch->Exists(userVerificationsSourceTable)) {
        LOG_INFO("source user verifications, table is already processed");
        return;
    }

    NYT::TTableSchema userVerificationsSchema;
    userVerificationsSchema.Strict(true);
    userVerificationsSchema.AddColumn(NYT::TColumnSchema().Name(F_HOST).Type(NYT::VT_STRING));
    userVerificationsSchema.AddColumn(NYT::TColumnSchema().Name(F_USER_ID).Type(NYT::VT_INT64));
    userVerificationsSchema.AddColumn(NYT::TColumnSchema().Name(F_USER_LOGIN).Type(NYT::VT_STRING));
    userVerificationsSchema.AddColumn(NYT::TColumnSchema().Name(F_VERIFICATION_TYPE).Type(NYT::VT_STRING));
    userVerificationsSchema.AddColumn(NYT::TColumnSchema().Name(F_VERIFICATION_TIME).Type(NYT::VT_INT64));

    NYT::ITransactionPtr tx = clientSearch->StartTransaction();

    const char *REGEX_VERIFIED = "^webmaster-verified-hosts.(\\d+)$";
    TString oldUserVerificationTable, newUserVerificationTable;
    if (!GetDailyTables(tx, weekConfig, config.TABLE_SOURCE_USER_VERIFICATIONS_PREFIX, REGEX_VERIFIED, "user verifications", oldUserVerificationTable, newUserVerificationTable)) {
        ythrow yexception() << "source user verifications, there is no tables";
    }

    TOpRunner(tx)
        .InputNode(oldUserVerificationTable)
        .InputNode(newUserVerificationTable)
        .OutputNode(NYT::TRichYPath(userVerificationsSourceTable).Schema(userVerificationsSchema))
        .ReduceBy(F_HOST)
        .MapReduce(new TExtractUserVerificationsMapper, new TExtractUserVerificationsReducer)
        .SortBy(F_HOST)
        .Sort(userVerificationsSourceTable)
    ;

    tx->Commit();
}

} //namespace NWebmaster
