#include "searchmap.h"
#include "parsers/json/json.h"

#include <saas/util/json/json.h>
#include <library/cpp/logger/global/global.h>

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

#include <util/generic/algorithm.h>
#include <util/stream/file.h>

using namespace NSearchMapParser;

TVector<NSearchMapParser::TSearchInformation> NSearchMapParser::TSearchCluster::GetAllHosts() const {
    TSet<NSearchMapParser::TSearchInformation> result;

    TVector<TSearchReplica>::const_iterator replica = Replicas.begin();
    for (; replica != Replicas.end(); ++replica)
        for (TSearchReplica::const_iterator interval = replica->begin(); interval != replica->end(); ++interval)
            Copy(interval->second.begin(), interval->second.end(), inserter(result, result.end()));

    return TVector<NSearchMapParser::TSearchInformation>(result.begin(), result.end());
}

void TSearchCluster::NormalizeIntervals() {
    for (auto& replica : Replicas) {
        for (auto& interval : replica) {
            for (auto& source : interval.second) {
                // source.Shards - оригинальный интервал из searchmap.json
                // interval - усеченный интервал, полученный после разбиения пересекающихся интервалов. Пересекающиеся интервалы
                // могут быть получены в ходе перешардирования сервиса.
                // В классе TSearchProxySearchMapProcessor бэкэнды агрегируются по оригинальным интервалам, усеченный интервал
                // там не используется. Данный метод позволяет заменить оригинальный интервал на усеченный
                // https://a.yandex-team.ru/arc/trunk/arcadia/saas/searchproxy/core/searchproxyserver.cpp?rev=3450071#L164
                source.Shards = interval.first;
            }
        }
    }
}

void TSearchMap::Compile(bool checkIndex, bool checkSearch) {
    ServiceMap.clear();
    THostsInfoChecker fullClusterChecker;
    for (auto& service : InternalSearchMap) {
        if (!service.Name)
            throw yexception() << "empty service name";
        TSet<TString> replicaNames;
        TSearchCluster& cluster = ServiceMap.insert(std::make_pair(service.Name, TSearchCluster(service))).first->second;
        for (TVector<TSearchMapReplica>::const_iterator replicaIter = service.Replicas.begin(); replicaIter != service.Replicas.end(); ++replicaIter) {
            if (!replicaNames.insert(replicaIter->Alias).second)
                throw yexception() << "replica " << replicaIter->Alias << " is defined twice in service " << service.Name;

            cluster.Replicas.push_back(TSearchReplica(service.GetShardsMax(), *replicaIter));
            TSearchReplica& replica = cluster.Replicas.back();
            TSearchReplica  searchVerifier(service.GetShardsMax(), *replicaIter);
            TSearchReplica  indexingVerifier(service.GetShardsMax(), *replicaIter);
            TSearchReplica  searchFiltrationVerifier(service.GetShardsMax(), *replicaIter);
            TSearchReplica  fetchVerifier(service.GetShardsMax(), *replicaIter);

            for (TVector<TSearchMapHost>::const_iterator hostIter = replicaIter->Hosts.begin(); hostIter != replicaIter->Hosts.end(); ++hostIter) {
                replica.AddHandler(hostIter->Shards, TSearchInformation(*hostIter));
                fullClusterChecker.AddHost(*hostIter);

                if (!hostIter->DisableSearch && !replica.DisableSearch) {
                    searchVerifier.AddHandler(hostIter->Shards, TSearchInformation(*hostIter));
                    if (!hostIter->DisableSearchFiltration)
                        searchFiltrationVerifier.AddHandler(hostIter->Shards, TSearchInformation(*hostIter));
                    if (!hostIter->DisableFetch)
                        fetchVerifier.AddHandler(hostIter->Shards, TSearchInformation(*hostIter));
                }
                if (!hostIter->DisableIndexing && !replica.DisableIndexing)
                    indexingVerifier.AddHandler(hostIter->Shards, TSearchInformation(*hostIter));
            }

            if (checkSearch && service.ShardsDispatcher->NeedFullCoverage()) {
                if (!searchVerifier.DoesCoverInterval(TInterval<TShardIndex>(0, service.GetShardsMax())))
                    throw yexception() << "service " << service.Name << " replica " << replica.Alias << " has gaps in search coverage";
                if (!searchFiltrationVerifier.DoesCoverInterval(TInterval<TShardIndex>(0, service.GetShardsMax())))
                    throw yexception() << "service " << service.Name << " replica " << replica.Alias << " has gaps in search filtration coverage";
                if (!fetchVerifier.DoesCoverInterval(TInterval<TShardIndex>(0, service.GetShardsMax())))
                    throw yexception() << "service " << service.Name << " replica " << replica.Alias << " has gaps in fetch coverage";
            }
            if (checkIndex && service.ShardsDispatcher->NeedFullCoverage() && !indexingVerifier.DoesCoverInterval(TInterval<TShardIndex>(0, service.GetShardsMax())))
                throw yexception() << "service " << service.Name << " replica " << replica.Alias << " has gaps in indexing coverage";
        }
    }
    fullClusterChecker.CheckOrThrow();
}

TString TSearchMap::SerializeToJson() const {
    NSearchMapParser::TJsonSearchMapParser jsmp(*this);
    return jsmp.SerializeToString();
}

NJson::TJsonValue TSearchMap::SerializeToJsonObject() const {
    NSearchMapParser::TJsonSearchMapParser jsmp(*this);
    return jsmp.SerializeToJson();
}

TSearchMap::TSearchMap(const TVector<TSearchMapService>& internalSearchMap, const TVector<TMetaService> metaSearchMap) {
    InternalSearchMap = internalSearchMap;
    MetaServices = metaSearchMap;
    Compile(false, false);
}

void NSearchMapParser::TSearchCluster::ProcessSearchMap(ISearchMapProcessor& processor) const {
    if (!processor.ProcessService(*this))
        return;
    for (TVector<TSearchReplica>::const_iterator replica = Replicas.begin();
        replica != Replicas.end(); ++replica)
        for (TSearchReplica::const_iterator interval = replica->begin(); interval != replica->end(); ++interval)
            for (TVector<TSearchInformation>::const_iterator host = interval->second.begin();
                host != interval->second.end(); ++host)
                processor.Do(*this, *replica, interval->first, *host);
}

TSearchMapReplica* TSearchMapService::GetReplica(const TString& configType) {
    for (ui32 r = 0; r < Replicas.size(); ++r) {
        if (Replicas[r].Alias == configType)
            return &Replicas[r];
    }
    return nullptr;
}

const TSearchMapReplica* TSearchMapService::GetReplica(const TString& configType) const {
    for (ui32 r = 0; r < Replicas.size(); ++r) {
        if (Replicas[r].Alias == configType)
            return &Replicas[r];
    }
    return nullptr;
}

TSearchMapReplica* TSearchMapService::AddReplica(const TString& configType) {
    TSearchMapReplica* checkReplica = GetReplica(configType);
    if (checkReplica)
        return checkReplica;
    TSearchMapReplica result;
    result.Alias = configType;
    Replicas.push_back(result);
    return &Replicas.back();
}

TVector<TSearchMapHost> TSearchMapService::GetSlots() const {
    TVector<TSearchMapHost> result;
    for (TVector<TSearchMapReplica>::const_iterator i = Replicas.begin(); i != Replicas.end(); ++i) {
        const TVector<TSearchMapHost>& replicaSlots = i->GetSlots();
        Copy(replicaSlots.begin(), replicaSlots.end(), inserter(result, result.end()));
    }
    return result;
}

TVector<TSearchMapHost> TSearchMapReplica::GetSlotsIntersect(NSearchMapParser::TShardIndex shardMin, NSearchMapParser::TShardIndex shardMax) const {
    TVector<TSearchMapHost> result;
    TInterval<TShardIndex> interval(shardMin, shardMax);
    for (ui32 i = 0; i < Hosts.size(); ++i) {
        if (Hosts[i].Shards.Intersection(interval))
            result.push_back(Hosts[i]);
    }
    return result;
}

TVector<TSearchMapHost> TSearchMapReplica::GetSlotsContainedIn(NSearchMapParser::TShardIndex shardMin, NSearchMapParser::TShardIndex shardMax) const {
    TVector<TSearchMapHost> result;
    TInterval<TShardIndex> interval(shardMin, shardMax);
    for (ui32 i = 0; i < Hosts.size(); ++i) {
        if (Hosts[i].Shards.IsContainedIn(interval))
            result.push_back(Hosts[i]);
    }
    return result;
}

void TSearchMapService::SetDefaults() {
    SearchPort = IndexerPort = 16000;
}

NSaasProto::TService TSearchMapService::SerializeToProto() const {
    NSaasProto::TService result = TServiceSpecificOptions::SerializeToProto();
    result.SetName(Name);
    for (auto& i : Replicas) {
        *result.AddConfigType() = i.SerializeToProto();
    }
    return result;
}

bool TSearchMapService::Deserialize(const NSaasProto::TService& service) {
    if (!TServiceSpecificOptions::Deserialize(service)) {
        return false;
    }

    Name = service.GetName();

    for (ui32 ct = 0; ct < service.ConfigTypeSize(); ++ct) {
        const NSaasProto::TConfigType& configType = service.GetConfigType(ct);
        if (!AddReplica(configType.GetName())->Deserialize(configType)) {
            return false;
        }
    }
    return true;
}

bool TSearchMapService::Deserialize(const NJson::TJsonValue& value) {
    try {
        *this = TSearchMapServiceParser("", value, DefaultParsingSettings).Get();
        return true;
    } catch (const yexception& e) {
        Cerr << "cannot deserialize host: " << e.what() << Endl;
        return false;
    }
}

NJson::TJsonValue TSearchMapService::Serialize() const {
    return TJsonSearchMapParser::SerializeService(*this);
}

void TSearchMapReplica::SetDefaults() {
    DisableSearch = false;
    DisableIndexing = false;
}

bool TSearchMapReplica::IsDefault() const {
    return !DisableSearch && !DisableIndexing && UpdateFrequency == TDuration::Zero();
}

NSaasProto::TConfigType TSearchMapReplica::SerializeToProto() const {
    NSaasProto::TConfigType result = TReplicaSpecificOptions::SerializeToProto();
    *result.MutableSlots() = TSlotsPool::SerializeToProto();
    return result;
}

bool TSearchMapReplica::Deserialize(const NSaasProto::TConfigType& configType) {
    if (!TReplicaSpecificOptions::Deserialize(configType))
        return false;
    TSlotsPool::Deserialize(configType.GetSlots());
    return true;
}

bool TSearchMapReplica::Deserialize(const NJson::TJsonValue& value) {
    try {
        *this = TSearchMapReplicaParser("", value, DefaultParsingSettings).Get();
        return true;
    } catch (const yexception& e) {
        Cerr << "cannot deserialize host: " << e.what() << Endl;
        return false;
    }
}

NJson::TJsonValue TSearchMapReplica::Serialize() const {
    return TJsonSearchMapParser::SerializeReplica(*this);
}

NJson::TJsonValue NSearchMapParser::TMetaService::SerializeToJson() const
{
    NJson::TJsonValue result;
    NSearchMapParser::TJsonSearchMapParser::SerializeCommonService(result, *this);
    NJson::TJsonValue componentsJson(NJson::JSON_ARRAY);
    for (auto& i : Components) {
        componentsJson.AppendValue(i.SerializeToJson());
    }
    result.InsertValue("Name", Name);
    result.InsertValue("Components", componentsJson);
    return result;
}

bool NSearchMapParser::TMetaService::Deserialize(const NJson::TJsonValue& info)
{
    Name = info["Name"].GetStringRobust();
    NSearchMapParser::TSearchMapCommonServiceParser(Name, info, NSearchMapParser::DefaultParsingSettings).FillCommonService(*this);
    NJson::TJsonValue::TArray comps;
    if (!info["Components"].GetArray(&comps))
        return false;

    for (auto& i : comps) {
        TMetaComponent component;
        if (!component.Deserialize(i))
            return false;
        Components.push_back(component);
    }
    return true;
}

NSearchMapParser::TSearchMapModifier::TSearchMapModifier()
    : OriginalService(nullptr)
    , OriginalReplica(nullptr)
{}

void NSearchMapParser::TSearchMapModifier::OnMetaService(const NSearchMapParser::TMetaService& info) {
    Result.GetMetaServices().push_back(info);
}

bool NSearchMapParser::TSearchMapModifier::OnService(const NSearchMapParser::TSearchMapService& service) {
    CloseService();
    OriginalService = &service;
    NewService = service;
    NewService.Replicas.clear();
    return true;
}

void NSearchMapParser::TSearchMapModifier::OnConfType(const NSearchMapParser::TSearchMapReplica& replica, const NSearchMapParser::TSearchMapService& /*service*/) {
    CloseReplica();
    OriginalReplica = &replica;
    NewReplica = replica;
    NewReplica.Hosts.clear();
    NewReplica.EndPointSets.clear();
}

void NSearchMapParser::TSearchMapModifier::OnHost(const NSearchMapParser::TSearchMapHost& host, const NSearchMapParser::TSearchMapReplica& /*replica*/, const NSearchMapParser::TSearchMapService& /*service*/) {
    NSearchMapParser::TSearchMapHost newHost = host;
    if (ModifyHost(newHost, *OriginalReplica, *OriginalService))
        NewReplica.Add(newHost);
}

void NSearchMapParser::TSearchMapModifier::OnEndpointSet(const NSearchMapParser::TSearchMapHost& host, const NSearchMapParser::TSearchMapReplica& /*replica*/, const NSearchMapParser::TSearchMapService& /*service*/) {
    NSearchMapParser::TSearchMapHost newHost = host;
    if (ModifyEndpointSet(newHost, *OriginalReplica, *OriginalService))
        NewReplica.Add(newHost);
}

bool NSearchMapParser::TSearchMapModifier::ModifyService(NSearchMapParser::TSearchMapService& service, const NSearchMapParser::TSearchMapService& /*originalService*/) {
    return !service.Replicas.empty();
}

bool NSearchMapParser::TSearchMapModifier::ModifyReplica(NSearchMapParser::TSearchMapReplica& replica, const NSearchMapParser::TSearchMapReplica& /*originalReplica*/, const NSearchMapParser::TSearchMapService& /*originalService*/) {
    return !replica.Hosts.empty();
}

void NSearchMapParser::TSearchMapModifier::CloseService() {
    CloseReplica();
    if (OriginalService && ModifyService(NewService, *OriginalService)) {
        Result.AddService(NewService.Name) = NewService;
        OriginalService = nullptr;
    }
}

void NSearchMapParser::TSearchMapModifier::CloseReplica() {
    if (OriginalReplica && ModifyReplica(NewReplica, *OriginalReplica, *OriginalService)) {
        *NewService.AddReplica(NewReplica.Alias) = NewReplica;
        OriginalReplica = nullptr;
    }
}

NSearchMapParser::TSearchMap& NSearchMapParser::TSearchMapModifier::Process(NSearchMapParser::TSearchMap sm) {
    Result.Clear();
    sm.Scan(*this);
    CloseService();
    return Result;
}

NSearchMapParser::TShardIndex NSearchMapParser::TServiceSpecificOptions::GetShardsMax() const {
    CHECK_WITH_LOG(ShardsDispatcher);
    return ShardsDispatcher->GetContext().GetShardsMax();
}
