#include "broadcast.h"
#include "url_params.h"

#include <saas/deploy_manager/server/client/client.h>
#include <saas/deploy_manager/scripts/common/deploy/deploy_builder.h>
#include <saas/deploy_manager/scripts/cluster/cluster_task.h>
#include <saas/library/daemon_base/actions_engine/controller_client.h>
#include <saas/library/searchmap/parsers/json/json.h>
#include <saas/util/external/dc.h>

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

namespace {
    inline void ScanJsonSearchMap(const NJson::TJsonValue& searchMapJson, NSearchMapParser::ISearchMapScannerCallback& callback) {
        NSearchMapParser::TJsonSearchMapParser jsmp(searchMapJson);
        const NSearchMapParser::TSearchMap searchMap = jsmp.GetSearchMap(false, false);
        DEBUG_LOG << "start broadcast requests... " << Endl;
        searchMap.Scan(callback);
    }
} // namespace

namespace NRTYDeploy {

    void TScriptBroadcast::PrepareJsonForReply(const TBroadcastSlotData& slot, NJson::TJsonValue& reply) {
        try {
            if (!Filter.Apply(reply, reply) && CodeReply != 500)
                CodeReply = 400;
            DoPrepareJsonForReply(slot, reply);
        } catch (...) {
            reply.InsertValue("Fail", CurrentExceptionMessage());
            CodeReply = 500;
        }
    }

    NJson::TJsonValue TScriptBroadcast::GetSlotControllerStatus(const TBroadcastSlotData& slot, NJson::TJsonValue& reply, const bool updateOnly) const {
        NRTYCluster::TSlotData sd;
        NRTYCluster::TSlotData::Parse(slot.SMHost.GetSlotName(), sd);
        NRTYDeploy::IControllersChecker::TSlotInfo slotInfo;
        if (ReplyHasGlobalServerStatus) {
            if (reply.Has("result") && reply["result"].Has("server_status_global")) {
                slotInfo = CommonData->GetControllersChecker().UpdateSlot(sd, reply["result"]["server_status_global"]);
            } else {
                slotInfo = CommonData->GetControllersChecker().FailSlot(sd);
            }
        } else {
            if (!updateOnly)
                slotInfo = CommonData->GetControllersChecker().CheckSlot(sd);
        }
        const NRTYDeploy::IControllersChecker::TControllerStatus& status = slotInfo.ControllerStatus;
        return status.SerializeToJson();

    }

    void TScriptBroadcast::RunAsync(TAutoPtr<IObjectInQueue> obj) {
        {
            TGuard<TMutex> g(Mutex);
            ActiveRequestsCount++;
            DEBUG_LOG << "Added " << ToString(ActiveRequestsCount) << " request" << Endl;
        }
        CommonData->SafeAddAndOwn(obj);
    }

    void TScriptBroadcast::AddReplyInfo(const TBroadcastSlotData& slot, const TString& reply) {
        DEBUG_LOG << "Receive broadcast reply from " << slot.SMHost.GetSlotName() << Endl;
        NJson::TJsonValue jsValue(NJson::JSON_MAP);
        if (!NJson::ReadJsonFastTree(reply, &jsValue))
            jsValue.InsertValue("text", reply);

        const bool updateOnly = !NeedStatus || (!Filter.GetFilters().empty() && !Filter.GetFilters().contains("$status$"));
        jsValue["$status$"] = GetSlotControllerStatus(slot, jsValue, updateOnly);
        TString DC = slot.SMHost.DC;
        if (DC == "" || DC == DC_NOT_DEFINED) {
            if (slot.SMHost.IsSd)
                DC = NSlotNameUtil::DCFromEndpointSet(slot.SMHost.Name);
            else
                DC = TDatacenterUtil::Instance().GetDatacenter(slot.SMHost.Name);
        }
        jsValue["$datacenter$"] = DC;
        const TString realHost= TDatacenterUtil::Instance().GetRealHost(slot.SMHost.Name);
        jsValue["$real_host$"] = realHost;
        jsValue["is_sd"] = slot.SMHost.IsSd;
        TString dcAlias;
        if (TDatacenterUtil::Instance().GetDatacenterAlias(DC, dcAlias))
            jsValue["$datacenter_alias$"] = dcAlias;
        jsValue["$shards_min$"] = slot.SMHost.Shards.GetMin();
        jsValue["$shards_max$"] = slot.SMHost.Shards.GetMax();
        jsValue["service"] = slot.ServiceName;
        jsValue["id"] = slot.SMHost.GetSlotName();
        jsValue["host"] = slot.SMHost.Name;
        jsValue["port"] = slot.SMHost.SearchPort;
        jsValue["interval"] = slot.SMHost.Shards.ToString();
        jsValue["disable_search"] = slot.SMHost.DisableSearch;
        jsValue["disable_search_filtration"] = slot.SMHost.DisableSearchFiltration;
        jsValue["disable_search_fetch"] = slot.SMHost.DisableFetch;
        jsValue["disable_indexing"] = slot.SMHost.DisableIndexing;
        PrepareJsonForReply(slot, jsValue);
        TGuard<TMutex> g(Mutex);
        ActiveRequestsCount--;
        Report[slot.ServiceName].InsertValue(slot.SMHost.GetSlotName(), std::move(jsValue));
        DEBUG_LOG << "Handled " << ToString(ActiveRequestsCount) << " request" << Endl;
        if (ActiveRequestsCount == 0) {
            CondVar.Signal();
        }
    }

    bool TScriptBroadcast::MakeReport(IOutputStream& os) {
        NJson::WriteJson(&os, &Report, true, true, false);
        return true;
    }

    bool TScriptBroadcast::OnService(const NSearchMapParser::TSearchMapService& info) {
        if (info.Name == Service || ((!Service || Service == "*") && !info.SkipPing)) {
            TGuard<TMutex> g(Mutex);
            Report[info.Name] = NJson::TJsonValue(NJson::JSON_MAP);
            return true;
        }
        return false;
    }

    void TScriptBroadcast::OnHost(const NSearchMapParser::TSearchMapHost& host, const NSearchMapParser::TSearchMapReplica& replica, const NSearchMapParser::TSearchMapService& service) {
        if (WithSd || !host.IsSd) {
            Requester.AddSource(TBroadcastSlotData(host, service.Name, replica.Alias));
        }
    }

    bool TScriptBroadcast::Process(IDeployInfoRequest& request) {
        CommonData = &request;
        CodeReply = 200;

        DEBUG_LOG << "start broadcast... " << Endl;
        if (!PrepareCustom(request)) {
            request.Output() << "HTTP/1.1 500 \r\n\r\n";
            request.Output() << "Can't prepare specific data";
            return true;
        }
        NRTYDeploy::TClusterTask::TCgiContext cgiContext = NRTYDeploy::TClusterTask::TCgiContext::Parse(request.GetRD().CgiParam);
        Filter.SetIgnoreUnknownPath(request.GetRD().CgiParam.Has("ignore_unknown_path") && FromString<bool>(request.GetRD().CgiParam.Get("ignore_unknown_path")));
        ServiceType = cgiContext.ServiceType;
        Service = cgiContext.Service;
        CType = cgiContext.CType;
        if (request.GetRD().CgiParam.Has("with_sd") && !FromString<bool>(request.GetRD().CgiParam.Get("with_sd"))) {
            WithSd = false;
        }
        if (!Command) {
            Command = request.GetRD().CgiParam.Print();
        }

        if (Command.Contains("command=get_status") || Command.Contains("command=get_info_server")) {
            ReplyHasGlobalServerStatus = true;
            if (HasNonEmptyFilter(Command)) {
                Command += "&filter=result.server_status_global";
            }
        }

        Filter.AddFilters(request.GetRD().CgiParam);

        if (!request.GetBlob().Empty()) {
            const TBlob& post = request.GetBlob();
            TMemoryInput mi(post.AsCharPtr(), post.Size());
            NJson::TJsonValue searchMapJson;
            if (!ReadJsonTree(&mi, &searchMapJson)) {
                request.Output() << "HTTP/1.1 500 \r\n\r\n";
                request.Output() << "Can't parse json value";
                return true;
            }
            const bool hasMultipleMaps = request.GetRD().CgiParam.Has("multiple_sm") && FromString<bool>(request.GetRD().CgiParam.Get("multiple_sm"));
            if (hasMultipleMaps) {
                if (!searchMapJson.IsArray()) {
                    request.Output() << "HTTP/1.1 500 \r\n\r\n";
                    request.Output() << "Multiple searchmaps should be in array";
                    return true;
                }
                for (const NJson::TJsonValue& jsonMap: searchMapJson.GetArray()) {
                    ScanJsonSearchMap(jsonMap, *this);
                }
            } else {
                ScanJsonSearchMap(searchMapJson, *this);
            }
            Report.InsertValue("content_hash", MD5::Calc(TStringBuf(post.AsCharPtr(), post.Size())));
        } else {
            THolder<NSearchMapParser::TSearchMap> searchMapHolder;
            const NSearchMapParser::TSearchMap* searchMap = nullptr;

            NRTYDeployInfo::IDeployComponentInfo::TPtr info = NRTYDeployInfo::IDeployComponentInfo::TFactory::Construct(ServiceType);
            info->SetInfo(&request, CType);

            if (!Service || Service == "*") {
                searchMap = &info->SearchMap();
            } else {
                searchMapHolder.Reset(new NSearchMapParser::TSearchMap(info->SearchMap(Service)));
                searchMap = searchMapHolder.Get();
            }
            if (!searchMap->GetInternalSearchMap().size()) {
                request.Output() << "HTTP/1.1 400 \r\n\r\n";
                request.Output() << "Incorrect service name for " << CType << "/" << Service << Endl;
                return true;
            }
            DEBUG_LOG << "start broadcast requests... " << Endl;
            searchMap->Scan(*this);
        }
        Requester.Wait();
        {
            TGuard<TMutex> g(Mutex);
            while(ActiveRequestsCount != 0) {
                DEBUG_LOG << "Waiting " << ToString(ActiveRequestsCount) << " request" << Endl;
                CondVar.WaitI(Mutex);
            }
        }
        TStringStream report;
        DEBUG_LOG << "start broadcast report... " << Endl;
        if (MakeReport(report)) {
            request.Output() << "HTTP/1.1 " << CodeReply << " \r\n\r\n";
            request.Output() << report.Str();
        } else {
            request.Output() << "HTTP/1.1 500 \r\n\r\n";
            request.Output() << "Can't build report";

        }
        DEBUG_LOG << "broadcast finished" << Endl;
        return true;
    }

    IScript::TFactory::TRegistrator<TScriptBroadcast> TScriptBroadcast::Registrator("broadcast");
}
