#include "shards_info.h"

#include <saas/library/behaviour/behaviour.h>
#include <saas/library/searchmap/parsers/parser.h>
#include <saas/util/json/json.h>
#include <saas/util/network/http_request.h>

#include <library/cpp/logger/global/global.h>

#include <util/stream/file.h>
#include <util/string/join.h>

namespace {
    TString ProcessHttpRequest(const TString& host, ui16 port, const TString& query) {
        NUtil::THttpRequest request(query);
        request.SetTimeout(TDuration::Minutes(1).MilliSeconds());
        NUtil::THttpReply reply = request.Execute(host, port);
        if (!reply.IsSuccessReply()) {
            ythrow yexception() << "Cannot process request " << host << ":" << port << query << " : " << reply.ErrorMessage();
        }
        return reply.Content();
    }

    TString GetSearchMapFromDM(const TString& host, ui16 port, const TString& ctype) {
        TString listConfQuery = "/list_conf?service_type=indexerproxy&service=indexerproxy&ctype=" + ctype;
        auto listConf = ProcessHttpRequest(host, port, listConfQuery);
        TString getSearchMapUrl;
        try {
            auto listConfJson = NUtil::JsonFromString(listConf);
            for (auto&& conf : listConfJson["files"].GetArray()) {
                if (conf["rename"] == "searchmap.json") {
                    getSearchMapUrl = conf["url"].GetString();
                    break;
                }
            }
            if (!getSearchMapUrl) {
                ythrow yexception() << "not in list";
            }
        } catch (...) {
            ythrow yexception() << "Cannot get SearchMap from list_conf: " << CurrentExceptionMessage();
        }
        return ProcessHttpRequest(host, port, "/get_conf?filename=" + getSearchMapUrl);
    }
};

namespace NSaas {

void TSearchMapInputSettings::Init(TYandexConfig::Section* section) {
    const TYandexConfig::Directives& directives = section->GetDirectives();
    directives.GetValue("Ctype", Ctype);
    directives.GetValue("DMPort", DMPort);
    directives.GetValue("DMHost", DMHost);
    directives.GetValue("StaticaPort", StaticaPort);
    directives.GetValue("StaticaHost", StaticaHost);
    directives.GetValue("StaticaQuery", StaticaQuery);
    directives.GetValue("File", File);
    directives.GetValue("UpdatePeriod", UpdatePeriod);
}

void TSearchMapInputSettings::Print(IOutputStream& so) const {
    so << "Ctype : " << Ctype << Endl;
    so << "DMPort : " << DMPort << Endl;
    so << "DMHost : " << DMHost << Endl;
    so << "StaticaPort : " << StaticaPort << Endl;
    so << "StaticaHost : " << StaticaHost << Endl;
    so << "StaticaQuery : " << StaticaQuery << Endl;
    so << "File : " << File << Endl;
    so << "UpdatePeriod : " << UpdatePeriod << Endl;
}

NSearchMapParser::TSearchMap GetSearchMap(const TSearchMapInputSettings& settings, const TSet<TString>& filterServices) {
    TString searchMapString = "";
    if (settings.DMHost) {
        try {
            searchMapString = GetSearchMapFromDM(settings.DMHost, settings.DMPort, settings.Ctype);
        } catch(...) {
            ERROR_LOG << "Cannot get SearchMap from deploy manager: " << CurrentExceptionMessage() << Endl;
        }
    }
    if (settings.StaticaHost && !searchMapString) {
        try {
            searchMapString = ProcessHttpRequest(settings.StaticaHost, settings.StaticaPort, settings.StaticaQuery);
        } catch(...) {
            ERROR_LOG << "Cannot get SearchMap from statica: " << CurrentExceptionMessage() << Endl;
        }
    }
    if (settings.File && !searchMapString) {
        try {
            TFileInput file(settings.File);
            searchMapString = file.ReadAll();
        } catch(...) {
            ERROR_LOG << "Cannot get SearchMap from file: " << CurrentExceptionMessage() << Endl;
        }
    }
    if (!searchMapString) {
        ythrow yexception() << "Cannot get SearchMap for ctype=" << settings.Ctype << Endl;
    }
    auto parser = NSearchMapParser::OpenSearchMap(searchMapString, filterServices);
    return parser->GetSearchMap();
}

void TServiceShards::Init(const TSearchMapInputSettings& searchMapSettings, const TString& service, bool ignorIndexDisabledSlots /*= true*/) {
    auto searchMap = GetSearchMap(searchMapSettings, TSet<TString>{service});
    Init(searchMap, service, ignorIndexDisabledSlots);
}

void TServiceShards::Init(const NSearchMapParser::TSearchMap& searchMap, const TString& service, bool ignorIndexDisabledSlots /*= true*/) {
    auto serviceConfig = searchMap.GetService(service);
    if (!serviceConfig) {
        ythrow yexception() << "service (" << service << ") not found in SearchMap";
    }
    FillShards(serviceConfig, ignorIndexDisabledSlots);
    ShardsDispatcher = serviceConfig->ShardsDispatcher;
    INFO_LOG << "Service shards info initialized." << Endl;
}

void TServiceShards::Init(const TString& searchMapString, const TString& service, bool ignorIndexDisabledSlots /*= true*/) {
    auto parser = NSearchMapParser::OpenSearchMap(searchMapString, {service});
    auto searchMap = parser->GetSearchMap();
    Init(searchMap, service, ignorIndexDisabledSlots);
}

void TServiceShards::Init(const TVector<NSearchMapParser::TShardsInterval>& shards, const TShardsDispatcher::TContext& dispatching) {
    Shards = shards;
    ShardsDispatcher = MakeAtomicShared<TShardsDispatcher>(dispatching);
}

bool TServiceShards::Initilized() const {
    return (!Shards.empty() && ShardsDispatcher);
}

void TServiceShards::FillShards(const NSearchMapParser::TSearchMapService* config, bool ignorIndexDisabledSlots) {
    TSet<TString> selectedShards;
    TSet<TString> shards;
    for (const auto& replica : config->Replicas) {
        for (const auto& host : replica.GetSlots()) {
            TString shardString = ToString(host.Shards);
            shards.insert(shardString);
            if (ignorIndexDisabledSlots && host.DisableIndexing) {
                continue;
            }
            if (!selectedShards.contains(shardString)) {
                Shards.push_back(host.Shards);
                selectedShards.insert(shardString);
            }
        }
    }
    INFO_LOG << "Loaded shards: " << JoinRange(",", selectedShards.begin(), selectedShards.end()) << Endl;
    TVector<TString> disabledShards;
    for (auto&& shard : shards) {
        if (!selectedShards.count(shard)) {
            disabledShards.push_back(shard);
        }
    }
    INFO_LOG << "Ignored shards: " << JoinRange(",", disabledShards.begin(), disabledShards.end()) << Endl;
}

TShardsDispatcher::TPtr TServiceShards::GetShardsDispatcher() const {
   return ShardsDispatcher;
}

TVector<NSearchMapParser::TShardsInterval> TServiceShards::GetShards() const {
    return Shards;
}

TVector<NSearchMapParser::TShardsInterval> TServiceShards::GetShards(const NRTYServer::TMessage& message) const {
    if (!ShardsDispatcher) {
        ythrow yexception() << "not initilized TServiceShards";
    }
    if (IsBroadcast(message)) {
        return GetShards();
    }
    auto idx = ShardsDispatcher->GetShard(message);

    TVector<NSearchMapParser::TShardsInterval> shards;
    for (const auto& shard : Shards) {
        if (ShardsDispatcher->CheckInterval(idx, shard)) {
            shards.push_back(shard);
        }
    }
    return shards;
}

TVector<NSearchMapParser::TShardsInterval> TServiceShards::GetShards(const NSaas::TAction& action) const {
    auto message = action.ToProtobuf();
    return GetShards(message);
}

bool TServiceShards::IsBroadcast(const NRTYServer::TMessage& message) const {
    return GetBehaviour(message.GetMessageType()).IsBroadcastMessage ||
        IsQueryProcessorMessage(message.GetMessageType(), message.GetDocument());
}

}
