#include "distributor.h"
#include "routeslist.h"

#include <saas/api/action.h>
#include <saas/library/daemon_base/daemon/messages.h>
#include <saas/library/daemon_base/metrics/metrics.h>
#include <saas/util/network/address.h>
#include <saas/indexerproxy/configs/config.h>
#include <saas/library/behaviour/behaviour.h>

TDistributorRequester::TDistributorRequester(const TDispatcherConfig& config)
    : Config(config)
{
    for (auto&& service : Config.GetOwner().GetServices()) {
        const auto it = Config.GetOwner().GetServiceInfo(service);
        VERIFY_WITH_LOG(it, "Incorrect service name %s", service.data());
        INFO_LOG << "Checking distributor client for service " << service << Endl;
        if (it->IndexingTarget == NSearchMapParser::Distributor) {
            AssertCorrectConfig(!it->GetDistributors().empty(), "Empty distributors string");

            TAtomicSharedPtr<NBus::TBusSessionConfig> configBS = new NBus::TBusSessionConfig();
            BusSessionConfigs[service] = configBS;
            const TProxyServiceConfig& configService = Config.GetOwner().GetServicesConfig().GetConfig(service);

            configBS->ConnectTimeout = configService.ConnectionTimeoutDuration.MilliSeconds();
            configBS->SendTimeout = configService.InteractionTimeoutDuration.MilliSeconds();
            configBS->RetryInterval = 0;
            configBS->NumRetries = configService.SendAttemptsCount;

            NRealTime::TDepositDistributorClient::TOptions options;
            options.Config = configBS.Get();
            options.Metrics = &GetGlobalMetrics();
            options.MetricsNamePrefix = service + "_";
            auto distributors = NRealTime::ParseDistributorString(it->GetDistributors());
            auto preresolved  = Preresolve(distributors);
            auto client = new NRealTime::TDepositDistributorClient(preresolved, options);
            Clients.insert(std::make_pair(service, client));
        }
    }
}

void TDistributorRequester::Send(const TString& service, const NRTYServer::TMessage& message, const NRealTime::TDepositDistributorClient::IHandler* handler) {
    auto client = Clients.find(service);
    if (client == Clients.end()) {
        throw yexception() << "no distributor client for service " << service;
    }
    auto info = Config.GetOwner().GetServiceInfo(service);
    if (!info) {
        throw yexception() << "incorrect service " << service;
    }

    NRealTime::TIndexedDocDeposit deposit;
    deposit.SetDoc(message.SerializeAsString());
    deposit.SetUrlKey(GetShard(*info, message));
    deposit.SetUrlId(NSaas::GetActionDescription(message));
    deposit.SetVersion(NSaas::GetActionVersion(message));
    deposit.AddStream(info->Stream);
    if (message.HasDocument())
        deposit.SetRank(message.GetDocument().GetFilterRank());

    for (auto&& value : message.GetDistributorAttributes()) {
        deposit.AddAttrs(value);
    }

    client->second->DepositAsync(deposit, handler);
}

NRealTime::TDistributors TDistributorRequester::Preresolve(const NRealTime::TDistributors& original) {
    NRealTime::TDistributors result;
    for (auto&& d : original) {
        if (NUtil::CanBeResolved(d.Host, d.Port)) {
            result.push_back(d);
        } else {
            ERROR_LOG << "Cannot resolve " << d << Endl;
        }
    }
    if (original.size()) {
        const float unresolved = float(original.size() - result.size()) / original.size();
        VERIFY_WITH_LOG(unresolved <= Config.GetMaxUnresolvedEndpoints(), "Critical number of unresolved distributors reached");
    }
    return result;
}
