
#include <util/draft/date.h>
#include <util/generic/queue.h>
#include <util/generic/size_literals.h>

#include <library/cpp/getopt/last_getopt.h>
#include <kernel/urlnorm/normalize.h>

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

#include <wmconsole/version3/library/jupiter/jupiter.h>
#include <wmconsole/version3/wmcutil/log.h>

#include <wmconsole/version3/library/jupiter/jupiter.h>
#include <wmconsole/version3/processors/siteservices/conf/config.h>
#include <wmconsole/version3/processors/siteservices/protos/services.pb.h>
#include <wmconsole/version3/wmcutil/yt/transfer_manager.h>
#include <sprav/protos/company.pb.h>
#include <sprav/protos/export.pb.h>

#include <library/cpp/json/json_reader.h>
#include <library/cpp/json/json_writer.h>

#include <yweb/antispam/common/owner/owner.h>
#include <limits.h>

#include "import_util.h"
#include "task_about_sprav.h"



namespace NWebmaster {

using namespace NJupiter;

static void fillSet(const NJson::TJsonValue::TArray &arr, TSet<TString> &set) {
    for (unsigned long i = 0; i < arr.size(); i++) {
        set.insert(arr[i].GetString());
    }
}

static NJson::TJsonValue setToJson(const TSet<TString> &set, int limit) {
    NJson::TJsonValue arr;
    int i = 0;
    for (const auto &it : set) {
        arr[i++] = ToString(it);

        if (i > limit) break;
    }

    return arr;
}

static NJson::TJsonValue setLongToJson(const TSet<long long> &set, int limit) {
    NJson::TJsonValue arr;
    int i = 0;
    for (const auto &it : set) {
        arr[i++] = it;

        if (i > limit) break;
    }

    return arr;
}

struct TSpravMapper : public NYT::IMapper<NYT::TTableReader<NYT::TNode>, NYT::TTableWriter<NProto::TPrepCompanyInfo>> {
    void Do(TReader *input, TWriter *output) override {
        THashMap<TString, size_t> counters;
        NSpravTDS::Company company;
        NSpravExport::TExportedCompany exportedCompany;

        for (; input->IsValid(); input->Next()) {
            const auto &row = input->GetRow();
            if (row.HasKey("is_exported") && row["is_exported"].AsBool()) {

                Y_PROTOBUF_SUPPRESS_NODISCARD company.ParseFromString(row["source_proto"].AsString());
                Y_PROTOBUF_SUPPRESS_NODISCARD exportedCompany.ParseFromString(row["export_proto"].AsString());
                TString urlStr;

                for (int i = 0; i < company.urls_size(); i++) {
                    const auto &url = company.urls(i);
                    if (!url.hide() && url.type() == NSpravTDS::CompanyUrl_Type::CompanyUrl_Type_Main) {
                        urlStr = GetOwner(ToString(url.value()));
                        break;
                    }
                }

                if (!urlStr.empty() && company.publishing_status() == NSpravTDS::Company_PublishingStatus::Company_PublishingStatus_Publish) {
                    NProto::TPrepCompanyInfo prepCompInfo;
                    NJson::TJsonValue value;

                    value["isHead"] = company.is_head();
                    value["permalink"] = row["permalink"].AsInt64();


                    if (company.has_address()) {
                        const auto &addr = company.address();

                        value["address"]["name"] = ToString(addr.formatted().value());
                        value["address"]["geoId"] = addr.geo_id();

                        if (addr.has_pos() && addr.pos().has_point()) {
                            value["address"]["pos"]["lon"] = addr.pos().point().lon();
                            value["address"]["pos"]["lat"] = addr.pos().point().lat();
                        }
                    }

                    if (exportedCompany.HasGeosearchData()) {
                        const auto &rating = exportedCompany.GetGeosearchData().GetRating();

                        for (int i = 0; i < rating.size(); i++) {
                            const auto &rr = rating[i];
                            if (rr.GetAref().equal("yandex")) {
                                value["rating"][i]["aref"] = ToString(rr.GetAref());
                                value["rating"][i]["amount"] = ToString(rr.GetAmount());
                                value["rating"][i]["rating"] = ToString(rr.GetRating());
                            }
                        }
                    }

                    int uc = 0;
                    for (int i = 0; i < company.names_size(); i++) {
                        const auto &name = company.names(i);
                        if (name.IsInitialized() && name.has_value() &&
                            name.type() == NSpravTDS::CompanyName::Main) {
                            value["names"][uc]["locale"] = ToString(name.value().lang().locale());
                            value["names"][uc]["value"] = ToString(name.value().value());

                            uc++;
                        }
                    }

                    uc = 0;
                    for (int i = 0; i < company.urls_size(); i++) {
                        const auto &url = company.urls(i);
                        if (url.IsInitialized() && !url.hide() &&
                            url.type() == NSpravTDS::CompanyUrl_Type::CompanyUrl_Type_Social) {
                            value["urlSocial"][uc++] = ToString(url.value());
                        }
                    }

                    uc = 0;
                    for (int i = 0; i < company.emails_size(); i++) {
                        const auto &email = company.emails(i);
                        if (email.IsInitialized() && email.has_value()) {
                            value["emails"][uc++] = ToString(email.value());
                        }
                    }

                    uc = 0;
                    for (int i = 0; i < company.phones_size(); i++) {
                        const auto &phone = company.phones(i);
                        if (phone.IsInitialized() && !phone.hide()) {
                            value["phones"][uc++] = ToString(phone.formatted());
                        }
                    }

                    uc = 0;
                    for (int i = 0; i < company.rubrics_size(); i++) {
                        const auto &rubric = company.rubrics(i);
                        if (rubric.IsInitialized()) {
                            value["rubrics"][uc]["id"] = rubric.rubric_id();
                            value["rubrics"][uc]["isMain"] = rubric.is_main();
                            uc++;
                        }
                    }

                    TString normalized;
                    if (NUrlNorm::NormalizeUrl(urlStr, normalized)){
                        prepCompInfo.set_mascotowner(GetOwner(normalized));
                        prepCompInfo.set_companyinfo(JsonToString(value));

                        output->AddRow(prepCompInfo);
                    }
                }
            }
        }
    }
};

REGISTER_MAPPER(TSpravMapper)

struct TSpravReducer : public NYT::IReducer<NYT::TTableReader<NProto::TPrepCompanyInfo>, NYT::TTableWriter<NProto::TPrepCompanyInfo>> {
    void Do(TReader *input, TWriter *output) override {
        NProto::TPrepCompanyInfo companyInfo;
        NJson::TJsonValue value;

        TSet<TString> urlSocial;
        TSet<TString> phones;
        TSet<TString> emails;
        TSet<long long> permalinks;

        for (; input->IsValid(); input->Next()) {
            const auto &row = input->GetRow();
            auto infoJson = StringToJson(ToString(row.companyinfo()));

            if (companyInfo.mascotowner().empty()) {
                companyInfo.set_mascotowner(row.mascotowner());
            }
            if (infoJson["urlSocial"].IsArray()){
                fillSet(infoJson["urlSocial"].GetArray(), urlSocial);
            }
            if (infoJson["phones"].IsArray()){
                fillSet(infoJson["phones"].GetArray(), phones);
            }
            if (infoJson["emails"].IsArray()){
                fillSet(infoJson["emails"].GetArray(), emails);
            }

            permalinks.insert(infoJson["permalink"].GetInteger());

            infoJson.EraseValue("urlSocial");
            infoJson.EraseValue("phones");
            infoJson.EraseValue("emails");

            value["names"] = infoJson["names"];
            value["rating"] = infoJson["rating"];
            value["permalink"] = infoJson["permalink"];
        }

        if (!urlSocial.empty()){
            value["urlSocial"] = setToJson(urlSocial, 10);
        }
        if (!phones.empty()){
            value["phones"] = setToJson(phones, 10);
        }
        if (!emails.empty()){
            value["emails"] = setToJson(emails, 10);
        }
        if (!permalinks.empty()){
            value["permalink"] = setLongToJson(permalinks, 10);
        }

        value.EraseValue("departments");
        companyInfo.set_companyinfo(JsonToString(value));
        output->AddRow(companyInfo);
    }
};

REGISTER_REDUCER(TSpravReducer)

int AboutSprav(int, const char **) {
    const auto &cfg = TConfig::CInstance();
    NYT::IClientPtr clientTo = NYT::CreateClient(cfg.MR_SERVER_HOST);
    NYT::IClientPtr clientFrom = NYT::CreateClient(cfg.MR_SERVER_HOST_SPRAV);

    NYTUtils::CreatePath(clientTo, cfg.TABLE_SERVICES_PREP_ROOT);
    NYTUtils::CreatePath(clientFrom, cfg.TABLE_SERVICES_PREP_ROOT);

    long tableTimestamp;
    long spravTimestamp;

    try {
        tableTimestamp = GetYtAttr(clientTo, cfg.TABLE_SERVICES_PREP_SPRAV, cfg.ATTR_UPDATE_SOURCE).AsInt64();
    } catch (yexception &) {
        tableTimestamp = 0l;
    }

    try {
        spravTimestamp = GetYtAttr(clientFrom, cfg.TABLE_SOURCE_SPRAV, cfg.ATTR_UPDATE_SPRAV_TS).AsInt64();
    } catch (yexception &) {
        spravTimestamp = tableTimestamp + 1L;
    }

    if (spravTimestamp <= tableTimestamp){
        LOG_INFO("Sprav db already updated!");
        return 0;
    }

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

    TMapCmd<TSpravMapper>(tx)
            .Input(TTable<NYT::TNode>(tx, cfg.TABLE_SOURCE_SPRAV))
            .Output(TTable<NProto::TPrepCompanyInfo>(tx, cfg.TABLE_SERVICES_PREP_SPRAV_INTERN))
            .OperationWeight(cfg.OPERATION_WEIGHT)
            .MaxRowWeight(128_MB)
            .Do();

    LOG_INFO("Map done");

    TSortCmd<NProto::TPrepCompanyInfo>
            (tx, TTable<NProto::TPrepCompanyInfo>(tx, cfg.TABLE_SERVICES_PREP_SPRAV_INTERN))
            .By({"MascotOwner"})
            .OperationWeight(cfg.OPERATION_WEIGHT)
            .Do();

    LOG_INFO("Sort done");

    TReduceCmd<TSpravReducer>(tx)
            .Input(TTable<NProto::TPrepCompanyInfo>(tx, cfg.TABLE_SERVICES_PREP_SPRAV_INTERN))
            .Output(TTable<NProto::TPrepCompanyInfo>(tx, cfg.TABLE_SERVICES_PREP_SPRAV))
            .OperationWeight(cfg.OPERATION_WEIGHT)
            .ReduceBy({"MascotOwner"})
            .MaxRowWeight(128_MB)
            .Do();

    LOG_INFO("Reduce done");

    TSortCmd<NProto::TPrepCompanyInfo>
            (tx, TTable<NProto::TPrepCompanyInfo>(tx, cfg.TABLE_SERVICES_PREP_SPRAV))
            .By({"MascotOwner"})
            .OperationWeight(cfg.OPERATION_WEIGHT)
            .Do();

    SetYtAttr(tx, cfg.TABLE_SERVICES_PREP_SPRAV, cfg.ATTR_UPDATE_SOURCE, spravTimestamp);

    tx->Commit();

    TTransferManager tManager(TConfigBase::GetYTToken());
    tManager.PostTaskAndWait(cfg.MR_SERVER_HOST_SPRAV, cfg.TABLE_SERVICES_PREP_SPRAV, cfg.MR_SERVER_HOST, cfg.TABLE_SERVICES_PREP_SPRAV);

    LOG_INFO("Sprav task done");

    return 0;
}

} //namespace NWebmaster
