package ru.yandex.direct.integrations.configuration;

import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Path;
import java.time.Duration;
import java.util.EnumMap;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import one.util.streamex.StreamEx;
import org.asynchttpclient.AsyncHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.TaskScheduler;

import ru.yandex.direct.advq.AdvqClient;
import ru.yandex.direct.advq.AdvqClientSettings;
import ru.yandex.direct.antifraud.client.AntifraudClient;
import ru.yandex.direct.appmetrika.AppMetrikaClient;
import ru.yandex.direct.asynchttp.FetcherSettings;
import ru.yandex.direct.asynchttp.ParallelFetcherFactory;
import ru.yandex.direct.audience.client.YaAudienceClient;
import ru.yandex.direct.balance.client.BalanceClient;
import ru.yandex.direct.balance.client.BalanceXmlRpcClient;
import ru.yandex.direct.balance.client.BalanceXmlRpcClientConfig;
import ru.yandex.direct.bangenproxy.client.BanGenProxyClient;
import ru.yandex.direct.bangenproxy.client.zenmeta.ZenMetaInfoApi;
import ru.yandex.direct.bangenproxy.client.zenmeta.ZenMetaInfoClient;
import ru.yandex.direct.bannercategoriesmultik.client.BannerCategoriesMultikClient;
import ru.yandex.direct.bannerstorage.client.BannerStorageClient;
import ru.yandex.direct.bannerstorage.client.BannerStorageClientConfiguration;
import ru.yandex.direct.bannerstorage.client.DummyBannerStorageClient;
import ru.yandex.direct.bannerstorage.client.RealBannerStorageClient;
import ru.yandex.direct.bannersystem.BannerSystemClient;
import ru.yandex.direct.bannersystem.BsHostType;
import ru.yandex.direct.bannersystem.BsUriFactory;
import ru.yandex.direct.blackbox.client.BlackboxClient;
import ru.yandex.direct.bmapi.client.BmapiClient;
import ru.yandex.direct.bmapi.configuration.BmapiConfiguration;
import ru.yandex.direct.bs.id.generator.BsDomainIdGeneratorClient;
import ru.yandex.direct.bs.id.generator.BsDomainIdGeneratorClientConfig;
import ru.yandex.direct.bsauction.BsTrafaretClient;
import ru.yandex.direct.bvm.client.BvmClient;
import ru.yandex.direct.bvm.client.BvmConfiguration;
import ru.yandex.direct.canvas.tools_client.CanvasToolsClient;
import ru.yandex.direct.common.configuration.CommonConfiguration;
import ru.yandex.direct.common.db.PpcPropertiesSupport;
import ru.yandex.direct.common.db.PpcProperty;
import ru.yandex.direct.common.db.PpcPropertyNames;
import ru.yandex.direct.common.liveresource.yav.YavLiveResource;
import ru.yandex.direct.config.DirectConfig;
import ru.yandex.direct.config.EssentialConfiguration;
import ru.yandex.direct.crypta.client.CryptaClient;
import ru.yandex.direct.crypta.client.impl.GrpcCryptaClient;
import ru.yandex.direct.dialogs.client.DialogsClient;
import ru.yandex.direct.display.landing.client.DisplayLandingClient;
import ru.yandex.direct.display.landing.client.DisplayLandingClientSettings;
import ru.yandex.direct.env.EnvironmentType;
import ru.yandex.direct.env.EnvironmentTypeProvider;
import ru.yandex.direct.expert.client.ExpertClient;
import ru.yandex.direct.geosearch.GeosearchClientSettings;
import ru.yandex.direct.geosuggest.client.GeoSuggestClient;
import ru.yandex.direct.http.smart.core.Smart;
import ru.yandex.direct.imagesearch.ImageSearchClient;
import ru.yandex.direct.intapi.client.IntapiSmartClient;
import ru.yandex.direct.inventori.InventoriCampaignsPredictionClient;
import ru.yandex.direct.inventori.InventoriClient;
import ru.yandex.direct.inventori.InventoriClientConfig;
import ru.yandex.direct.landlord.client.LandlordClient;
import ru.yandex.direct.landlord.client.LandlordConfiguration;
import ru.yandex.direct.libs.collections.CollectionsClient;
import ru.yandex.direct.libs.curator.CuratorFrameworkProvider;
import ru.yandex.direct.libs.laas.LaasClient;
import ru.yandex.direct.libs.video.VideoClient;
import ru.yandex.direct.libs.video.VideoClientConfig;
import ru.yandex.direct.liveresource.CachingLiveResource;
import ru.yandex.direct.liveresource.LiveResource;
import ru.yandex.direct.liveresource.LiveResourceFactory;
import ru.yandex.direct.liveresource.PollingLiveResourceWatcher;
import ru.yandex.direct.liveresource.provider.LiveResourceFactoryBean;
import ru.yandex.direct.market.client.MarketClient;
import ru.yandex.direct.market.client.http.MarketHttpClient;
import ru.yandex.direct.market.client.http.MarketHttpClient.MarketConfiguration;
import ru.yandex.direct.mediascope.MediascopeClient;
import ru.yandex.direct.mediascope.MediascopeClientConfig;
import ru.yandex.direct.metrika.client.MetrikaClient;
import ru.yandex.direct.metrika.client.MetrikaConfiguration;
import ru.yandex.direct.metrika.client.MetrikaHelper;
import ru.yandex.direct.metrika.client.asynchttp.MetrikaAsyncHttpClient;
import ru.yandex.direct.moderation.client.ModerationClient;
import ru.yandex.direct.moderation.client.ModerationClientConfiguration;
import ru.yandex.direct.organizations.swagger.OrganizationsClient;
import ru.yandex.direct.pokazometer.PokazometerClient;
import ru.yandex.direct.richcontent.RichContentClient;
import ru.yandex.direct.rotor.client.RotorClient;
import ru.yandex.direct.searchqueryrecommendation.SearchQueryRecommendationClient;
import ru.yandex.direct.sender.YandexSenderClient;
import ru.yandex.direct.sender.YandexSenderConfig;
import ru.yandex.direct.solomon.SolomonUtils;
import ru.yandex.direct.staff.client.StaffClient;
import ru.yandex.direct.staff.client.model.StaffConfiguration;
import ru.yandex.direct.takeout.client.TakeoutClient;
import ru.yandex.direct.telegram.client.TelegramClient;
import ru.yandex.direct.telephony.client.TelephonyClient;
import ru.yandex.direct.tvm.TvmIntegration;
import ru.yandex.direct.tvm.TvmService;
import ru.yandex.direct.uaas.UaasClient;
import ru.yandex.direct.uaas.UaasClientConfig;
import ru.yandex.direct.utils.HostPort;
import ru.yandex.direct.utils.io.FileUtils;
import ru.yandex.direct.xiva.client.XivaClient;
import ru.yandex.direct.xiva.client.XivaConfig;
import ru.yandex.direct.yasms.YaSmsClient;
import ru.yandex.direct.yasms.YaSmsClientCommon;
import ru.yandex.direct.yasms.YaSmsClientImpl;
import ru.yandex.direct.yasms.YaSmsClientWeb;
import ru.yandex.direct.yql.client.YqlClient;
import ru.yandex.direct.ytwrapper.client.YtProvider;
import ru.yandex.direct.zorafetcher.ZoraFetcher;
import ru.yandex.direct.zorafetcher.ZoraFetcherSettings;
import ru.yandex.direct.zorafetcher.ZoraGoRequestCreator;
import ru.yandex.direct.zorafetcher.ZoraOnlineRequestCreator;

import static ru.yandex.direct.common.configuration.CommonConfiguration.GO_ZORA_ASYNC_HTTP_CLIENT;
import static ru.yandex.direct.common.db.PpcPropertyNames.TELEPHONY_PLAYBACK_ID;
import static ru.yandex.direct.config.EssentialConfiguration.CONFIG_SCHEDULER_BEAN_NAME;
import static ru.yandex.direct.solomon.SolomonUtils.SOLOMON_REGISTRY;
import static ru.yandex.direct.utils.CommonUtils.nvl;

@Configuration
@Import({EssentialConfiguration.class, CommonConfiguration.class})
public class IntegrationsConfiguration {
    public static final String METRIKA_HELPER = "metrikaHelper";
    public static final String METRIKA_CLIENT = "metrikaClient";
    public static final String CRYPTA_CLIENT = "cryptaClient";
    public static final String EXPERT_CLIENT = "expertClient";
    public static final String SEARCH_QUERY_RECOMMENDATION_CLIENT = "searchQueryRecommendationClient";
    public static final String RICH_CONTENT_CLIENT = "richContentClient";
    public static final String BANGEN_PROXY_CLIENT = "banGenProxyClient";
    public static final String IMAGE_SEARCH_CLIENT = "imageSearchClient";
    public static final String ORGANIZATION_CLIENT = "organizationClient";
    public static final String TELEPHONY_CLIENT = "telephonyClient";
    public static final String YA_SMS_CLIENT_IMPL = "yaSmsClientImpl";
    public static final String YA_SMS_CLIENT_COMMON = "yaSmsClientCommon";
    public static final String YA_SMS_CLIENT_WEB = "yaSmsClientWeb";
    public static final String MARKET_CLIENT = "marketClient";
    public static final String BANNER_SYSTEM_CLIENT = "bannerSystemClient";
    public static final String CANVAS_CLIENT = "canvasClient";
    public static final String BMAPI_CLIENT = "bmapiClient";
    public static final String BS_TRAFARET_AUCTION_CLIENT = "bsTrafaretAuctionClient";
    public static final String BS_TRAFARET_AUCTION_CLIENT_WEB = "bsTrafaretAuctionClientWeb";
    public static final String POKAZOMETER_CLIENT = "pokazometerClient";
    public static final String ADVQ_CLIENT = "advqClient";
    public static final String BLACKBOX_EXECUTOR_SERVICE = "blackboxExecutorService";
    public static final String CURATOR_FRAMEWORK_PROVIDER = "CuratorFrameworkProvider";
    public static final String ZORA_FETCHER_METRIKA_LABEL = "zora_fetcher";
    public static final String UAAS_CLIENT = "uaasClient";
    public static final String BS_DOMAIN_ID_GENERATOR_CLIENT = "bsDomainIdGeneratorClient";
    public static final String YA_AUDIENCE_CLIENT = "yaAudienceClient";
    public static final String YA_AUDIENCE_TOKEN_PROVIDER = "yaAudienceTokenProvider";
    public static final String XIVA_CLIENT = "xivaClient";
    public static final String ROTOR_CLIENT = "rotorClient";
    public static final String ROTOR_TRACKING_URL_ANDROID_CLIENT = "rotorTrackingUrlAndroidClient";
    public static final String ROTOR_TRACKING_URL_IOS_CLIENT = "rotorTrackingUrlIosClient";
    public static final String BANNER_CATEGORIES_MULTIK_CLIENT = "bannerCategoriesMultikClient";
    public static final String ZEN_META_INFO_CLIENT = "zenMetaInfoClient";
    public static final String TELEGRAM_CLIENT_DIRECT_FEATURE = "telegramClientDirectFeature";
    public static final String PRODUCTION_INTAPI_SMART_CLIENT = "productionIntapiSmartClient";
    public static final String ANTIFRAUD_CLIENT = "antifraudClient";

    private static final int MAX_CONNECTIONS = 100;

    private static final long AUDIENCE_TOKEN_CHECK_RATE = TimeUnit.MINUTES.toMillis(5); // 5 minutes

    private final DirectConfig directConfig;

    public IntegrationsConfiguration(DirectConfig directConfig) {
        this.directConfig = directConfig;
    }

    @Bean(name = METRIKA_CLIENT)
    MetrikaClient metrikaClient(EnvironmentType environmentType, AsyncHttpClient asyncHttpClient,
                                TvmIntegration tvmIntegration, PpcPropertiesSupport ppcPropertiesSupport) {
        DirectConfig metrikaConfig = directConfig.getBranch("metrika");
        String intapiUrl = metrikaConfig.getString("intapi_url");
        String audienceUrl = metrikaConfig.getString("audience_url");
        String metrikaUrl = metrikaConfig.getString("metrika_url");
        MetrikaConfiguration metrikaConfiguration =
                new MetrikaConfiguration(intapiUrl, audienceUrl, metrikaUrl);
        //игнорим невалидность сертификата не в проде и не престэйбле
        boolean isProd = environmentType.isProductionOrPrestable();
        if (!isProd) {
            metrikaConfiguration.setIgnoreSslErrors(true);
        }

        return new MetrikaAsyncHttpClient(metrikaConfiguration, asyncHttpClient, tvmIntegration,
                ppcPropertiesSupport, isProd);
    }

    @Bean(name = METRIKA_HELPER)
    MetrikaHelper metrikaHelper(EnvironmentType environmentType, AsyncHttpClient asyncHttpClient,
                                TvmIntegration tvmIntegration, PpcPropertiesSupport ppcPropertiesSupport) {
        return new MetrikaHelper(metrikaClient(environmentType, asyncHttpClient, tvmIntegration,
                ppcPropertiesSupport));
    }

    @Bean(name = MARKET_CLIENT)
    MarketClient marketClient(AsyncHttpClient asyncHttpClient, TvmIntegration tvmIntegration) {
        DirectConfig webmasterConfig = directConfig.getBranch("market");
        String marketMbiUrl = webmasterConfig.getString("market_mbi_url");
        int serviceId = webmasterConfig.getInt("tvm_market_mbi_id");
        TvmService tvmService = TvmService.fromIdStrict(serviceId);
        MarketConfiguration marketConfiguration = new MarketConfiguration(tvmService, marketMbiUrl);
        ParallelFetcherFactory parallelFetcherFactory =
                new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings());
        return new MarketHttpClient(marketConfiguration, parallelFetcherFactory, tvmIntegration);
    }

    @Bean(name = CRYPTA_CLIENT)
    CryptaClient cryptaClient() {
        var cryptaConfig = directConfig.getBranch("crypta");
        var address = cryptaConfig.getString("crypta_suggester_address");

        return new GrpcCryptaClient(HostPort.fromString(address));
    }

    @Bean
    public StaffClient staffClient(AsyncHttpClient asyncHttpClient, TvmIntegration tvmIntegration) {
        DirectConfig staffConfig = directConfig.getBranch("staff");
        String staffApiUrl = staffConfig.getString("staff_api_url");
        String gapsApiUrl = staffConfig.getString("gaps_api_url");
        StaffConfiguration staffConfiguration = new StaffConfiguration(staffApiUrl, gapsApiUrl, null);
        FetcherSettings fetcherSettings = new FetcherSettings()
                .withParallel(staffConfig.getInt("parallel"))
                .withConnectTimeout(staffConfig.getDuration("connection_timeout"))
                .withGlobalTimeout(staffConfig.getDuration("global_timeout"))
                .withRequestTimeout(staffConfig.getDuration("request_timeout"))
                .withRequestRetries(staffConfig.getInt("request_retries"));
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        return new StaffClient(staffConfiguration, fetcherFactory, tvmIntegration, true);
    }

    @Bean
    @Lazy
    public YqlClient yqlClient(
            @Value("${yql.api_host}") String apiHost,
            YtProvider ytProvider,
            AsyncHttpClient asyncHttpClient
    ) {
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings());
        return new YqlClient(apiHost, ytProvider, fetcherFactory);
    }

    @Bean
    @Lazy
    BlackboxClient blackboxClient() {
        return IntegrationsUtils.configureBlackboxClient(directConfig, "blackbox", MAX_CONNECTIONS);
    }

    @Bean(name = BLACKBOX_EXECUTOR_SERVICE)
    @Lazy
    ExecutorService executorService() {
        final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("blackbox-thread-%d").build();
        // Avg 8-10 rps, 99 perc is at 0.63s per request, x2 + thread timeout for slow request processing
        final ThreadPoolExecutor executor = new ThreadPoolExecutor(20, 20,
                60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), threadFactory);
        executor.allowCoreThreadTimeOut(true);
        return executor;
    }

    @Bean
    @Lazy
    BannerSystemClient bannerSystemClient(AsyncHttpClient asyncHttpClient) {
        DirectConfig bsConfig = directConfig.getBranch("bannersystem.url_factory");
        EnumMap<BsHostType, String> prod = extractBsHostSettings(bsConfig, "prod");
        EnumMap<BsHostType, String> preprod = extractBsHostSettings(bsConfig, "preprod");

        BsUriFactory bsUriFactory = new BsUriFactory(prod, preprod);

        FetcherSettings settings = new FetcherSettings()
                .withRequestTimeout(BannerSystemClient.BANNER_SYSTEM_CLIENT_REQUEST_TIMEOUT);
        var parallelFetcherFactory = new ParallelFetcherFactory(asyncHttpClient, settings);

        return new BannerSystemClient(bsUriFactory, parallelFetcherFactory);
    }

    private EnumMap<BsHostType, String> extractBsHostSettings(DirectConfig bsConfig, String label) {
        DirectConfig labelConfig = bsConfig.getBranch(label);
        return StreamEx.of(BsHostType.values())
                .mapToEntry(type -> labelConfig.findString(type.toString().toLowerCase()).orElse(null))
                .filterValues(Objects::nonNull)
                .toCustomMap(() -> new EnumMap<>(BsHostType.class));
    }

    @Bean(name = ADVQ_CLIENT)
    @Lazy
    public AdvqClient advqClient(AdvqClientSettings settings, AsyncHttpClient asyncHttpClient) {
        return new AdvqClient(settings, asyncHttpClient);
    }

    @Bean
    public AdvqClientSettings advqClientSettings(
            @Value("${advq.host}") String host,
            @Value("${advq.port}") int port,
            @Value("${advq.customer_name}") String customerName,
            @Value("${advq.check_min_hits_path}") String checkMinHitsPath,
            @Value("${advq.check_min_hits_chunk_size}") Integer checkMinHitsChunkSize,
            @Value("${advq.search_path}") String searchPath,
            @Value("${advq.search_chunk_size}") Integer searchChunkSize,
            @Value("${advq.video_host}") String videoHost,
            @Value("${advq.video_port}") int videoPort,
            @Value("${advq.video_search_path}") String videoSearchPath,
            @Value("${advq.video_search_chunk_size}") Integer videoSearchChunkSize) {
        DirectConfig advqConfig = directConfig.getBranch("advq");
        FetcherSettings fetcherSettings = new FetcherSettings()
                .withParallel(advqConfig.getInt("parallel"))
                .withFailFast(true)
                .withRequestRetries(advqConfig.getInt("requestRetries"))
                .withRequestTimeout(advqConfig.getDuration("timeout"));
        return new AdvqClientSettings()
                .withHost(host)
                .withPort(port)
                .withCustomerName(customerName)
                .withCheckMinHitsPath(checkMinHitsPath)
                .withCheckMinHitsChunkSize(nvl(checkMinHitsChunkSize, 5))
                .withSearchPath(searchPath)
                .withSearchChunkSize(nvl(searchChunkSize, 5))
                .withVideoHost(videoHost)
                .withVideoPort(videoPort)
                .withVideoSearchPath(videoSearchPath)
                .withVideoSearchChunkSize(videoSearchChunkSize)
                .withFetcherSettings(fetcherSettings);
    }

    @Bean
    @Lazy
    ModerationClient moderationClient(AsyncHttpClient asyncHttpClient) {
        DirectConfig moderationConfig = directConfig.getBranch("moderation");
        String jsonRpcUrl = moderationConfig.getBranch("urls").getString("jsonrpc");
        return new ModerationClient(new ModerationClientConfiguration(jsonRpcUrl), asyncHttpClient);
    }

    @Bean
    @Lazy
    public BalanceClient balanceClient() throws MalformedURLException {

        DirectConfig balanceConfig = directConfig.getBranch("balance");

        BalanceXmlRpcClientConfig config =
                new BalanceXmlRpcClientConfig(new URL(balanceConfig.getString("serviceUrl")))
                        .withRequestTimeout(balanceConfig.getDuration("request_timeout"))
                        .withMaxRetries(balanceConfig.getInt("max_retries"));

        BalanceXmlRpcClient client = new BalanceXmlRpcClient(config);

        BalanceXmlRpcClientConfig configSimple =
                new BalanceXmlRpcClientConfig(new URL(balanceConfig.getString("serviceUrlSimple")))
                        .withRequestTimeout(balanceConfig.getDuration("request_timeout"))
                        .withMaxRetries(balanceConfig.getInt("max_retries"));

        BalanceXmlRpcClient clientSimple = new BalanceXmlRpcClient(configSimple);

        return new BalanceClient(client, clientSimple);
    }

    @Bean
    @Lazy
    PokazometerClient pokazometerClient(@Autowired AsyncHttpClient asyncHttpClient) {
        DirectConfig pokazometerConfig = directConfig.getBranch("pokazometer");
        String pokazometerUrl = pokazometerConfig.getString("url");
        FetcherSettings fetcherSettings = new FetcherSettings()
                .withParallel(pokazometerConfig.getInt("parallel"))
                .withFailFast(true)
                .withRequestRetries(pokazometerConfig.getInt("requestRetries"))
                .withRequestTimeout(pokazometerConfig.getDuration("timeout"))
                .withSoftTimeout(pokazometerConfig.getDuration("softTimeout"))
                .withConnectTimeout(pokazometerConfig.getDuration("connectionTimeout"));
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        return new PokazometerClient(fetcherFactory, pokazometerUrl);
    }

    @Bean(name = BS_TRAFARET_AUCTION_CLIENT)
    @Lazy
    BsTrafaretClient bsTrafaretAuctionClient(AsyncHttpClient asyncHttpClient,
                                             PpcPropertiesSupport ppcPropertiesSupport) {
        DirectConfig bsAuctionConfig = directConfig.getBranch("bsauction-trafaret");
        return bsTrafaretAuctionClient(asyncHttpClient, ppcPropertiesSupport,
                bsAuctionConfig, BS_TRAFARET_AUCTION_CLIENT);
    }

    @Bean(name = BS_TRAFARET_AUCTION_CLIENT_WEB)
    @Lazy
    BsTrafaretClient bsTrafaretAuctionClientWeb(AsyncHttpClient asyncHttpClient,
                                                PpcPropertiesSupport ppcPropertiesSupport) {
        DirectConfig bsAuctionConfig = directConfig.getBranch("bsauction-trafaret-web");
        return bsTrafaretAuctionClient(asyncHttpClient, ppcPropertiesSupport,
                bsAuctionConfig, BS_TRAFARET_AUCTION_CLIENT_WEB);
    }

    private BsTrafaretClient bsTrafaretAuctionClient(AsyncHttpClient asyncHttpClient,
                                                     PpcPropertiesSupport ppcPropertiesSupport,
                                                     DirectConfig bsAuctionConfig,
                                                     String solomonMetricLabel) {
        String bsAuctionUrl = bsAuctionConfig.getString("url");
        FetcherSettings fetcherSettings = new FetcherSettings()
                .withParallel(bsAuctionConfig.getInt("parallel"))
                .withFailFast(true)
                .withTotalRetriesCoef(0.5)
                .withRequestRetries(bsAuctionConfig.getInt("requestRetries"))
                .withRequestTimeout(bsAuctionConfig.getDuration("timeout"))
                .withMetricRegistry(SOLOMON_REGISTRY.subRegistry("parallel_fetcher", solomonMetricLabel));
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        PpcProperty<Double> softTimeoutSecondsProperty =
                ppcPropertiesSupport.get(PpcPropertyNames.BS_AUCTION_SOFT_TIMEOUT_SECONDS, Duration.ofMinutes(1));
        Supplier<Duration> softTimeoutSupplier = () -> {
            Double softTimeoutSeconds = softTimeoutSecondsProperty.get();
            if (softTimeoutSeconds != null) {
                return Duration.ofMillis((long) (softTimeoutSeconds * 1_000));
            } else {
                return bsAuctionConfig.getDuration("softTimeout");
            }
        };
        return new BsTrafaretClient(fetcherFactory, bsAuctionUrl, "bsrank.yandex.ru", softTimeoutSupplier);
    }

    @Bean
    @Lazy
    public DialogsClient dialogsClient(AsyncHttpClient asyncHttpClient, TvmIntegration tvmIntegration) {
        String url = directConfig.getString("dialogs-api.url");
        int tvmAppId = directConfig.getInt("dialogs-api.tvm_app_id");
        TvmService tvmService = TvmService.fromIdStrict(tvmAppId);
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings());
        return new DialogsClient(url, tvmService, fetcherFactory, tvmIntegration);
    }

    @Bean
    @Lazy
    public DisplayLandingClient displayLandingClient(AsyncHttpClient httpClient,
                                                     PpcPropertiesSupport ppcPropertiesSupport,
                                                     LiveResourceFactoryBean liveResourceFactoryBean) {
        DirectConfig config = directConfig.getBranch("display.landing");
        var fetcherSettings = new FetcherSettings()
                .withRequestTimeout(config.getDuration("request_timeout"))
                .withRequestRetries(config.getInt("request_retries"));
        ParallelFetcherFactory factory = new ParallelFetcherFactory(httpClient, fetcherSettings);
        var tokenResource = config.getString("auth_token");
        var token = tokenResource != null && !tokenResource.isEmpty()
                ? liveResourceFactoryBean.get(tokenResource).getContent().trim()
                : null;
        DisplayLandingClientSettings settings = new DisplayLandingClientSettings(
                config.getString("submissions_get_url"),
                config.getString("submissions_get_url_new"),
                token);
        PpcProperty<Boolean> prop = ppcPropertiesSupport.get(PpcPropertyNames.USE_NEW_DIRECT_SUBMISSIONS_URL,
                Duration.ofMinutes(1));
        return new DisplayLandingClient(settings, factory, () -> prop.getOrDefault(false));
    }

    @Bean
    @Lazy
    public InventoriClient inventoriClient(AsyncHttpClient asyncHttpClient) {
        final DirectConfig config = directConfig.getBranch("inventori");
        final Duration requestTimeout = config.findDuration("request_timeout")
                .orElse(Duration.ofSeconds(60));
        final String inventoriServiceBaseUrl = config.getString("url");

        return new InventoriClient(asyncHttpClient,
                new InventoriClientConfig(inventoriServiceBaseUrl, 1, requestTimeout, 1));
    }

    @Bean
    @Lazy
    public InventoriCampaignsPredictionClient inventoriCampaignsPredictionClient() {
        final DirectConfig config = directConfig.getBranch("inventori");
        final String inventoriURL = config.getString("url") + config.getString("general_campaigns_prediction");
        return new InventoriCampaignsPredictionClient(inventoriURL);
    }

    @Bean
    @Lazy
    public GeosearchClientSettings geosearchClientSettings() {
        DirectConfig config = directConfig.getBranch("geosearch");
        String apiUrl = config.getString("api_url");
        String origin = config.getString("origin");
        Duration requestTimeout = config.getDuration("request_timeout");
        int parallel = config.getInt("parallel");
        int retryCount = config.getInt("request_retries");
        Integer tvmAppId = config.getInt("tvm_app_id");
        return new GeosearchClientSettings(apiUrl, origin, retryCount, requestTimeout, parallel, tvmAppId);
    }

    @Bean(name = EXPERT_CLIENT)
    @Lazy
    public ExpertClient expertClient(AsyncHttpClient asyncHttpClient, TvmIntegration tvmIntegration) {
        String url = directConfig.getString("expert-api.url");
        int tvmAppId = directConfig.getInt("expert-api.tvm_app_id");
        TvmService tvmService = TvmService.fromIdStrict(tvmAppId);
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings());
        return new ExpertClient(url, tvmService, fetcherFactory, tvmIntegration);
    }

    @Bean(name = SEARCH_QUERY_RECOMMENDATION_CLIENT)
    @Lazy
    public SearchQueryRecommendationClient searchQueryRecommendationClient(
            @Value("${search_query_rec.url}") String baseUrl,
            AsyncHttpClient asyncHttpClient
    ) {
        FetcherSettings fetcherSettings = new FetcherSettings()
                .withFailFast(true)
                .withRequestRetries(2)
                .withSoftTimeout(Duration.ofSeconds(10))
                .withRequestTimeout(Duration.ofSeconds(20));
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        return new SearchQueryRecommendationClient(baseUrl, fetcherFactory);
    }

    @Bean(name = RICH_CONTENT_CLIENT)
    @Lazy
    public RichContentClient richContentClient(@Value("${rich_content_api.url}") String baseUrl,
                                               AsyncHttpClient asyncHttpClient) {
        FetcherSettings fetcherSettings = new FetcherSettings()
                .withFailFast(true)
                .withRequestRetries(2)
                .withSoftTimeout(Duration.ofSeconds(10))
                .withRequestTimeout(Duration.ofSeconds(20));
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        return new RichContentClient(baseUrl, fetcherFactory);
    }

    @Bean(name = BANGEN_PROXY_CLIENT)
    @Lazy
    public BanGenProxyClient banGenProxyClient(@Value("${bangen_proxy.url}") String baseUrl,
                                               AsyncHttpClient asyncHttpClient,
                                               TvmIntegration tvmIntegration) {
        FetcherSettings fetcherSettings = new FetcherSettings()
                .withFailFast(true)
                .withRequestRetries(2)
                .withSoftTimeout(Duration.ofSeconds(10))
                .withRequestTimeout(Duration.ofSeconds(20));
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        return new BanGenProxyClient(baseUrl, fetcherFactory, tvmIntegration);
    }

    @Bean(name = ZEN_META_INFO_CLIENT)
    @Lazy
    public ZenMetaInfoClient zenMetaInfoClient(AsyncHttpClient asyncHttpClient) {
        final String baseUrl = "https://zen.yandex.ru";
        final String profileName = "zen_meta_info_client";
        final FetcherSettings fetcherSettings = new FetcherSettings()
                .withFailFast(true)
                .withRequestRetries(2)
                .withSoftTimeout(Duration.ofSeconds(10))
                .withRequestTimeout(Duration.ofSeconds(20));
        final ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        final ZenMetaInfoApi api = Smart.builder()
                .withParallelFetcherFactory(fetcherFactory)
                .withProfileName(profileName)
                .withBaseUrl(baseUrl)
                .build()
                .create(ZenMetaInfoApi.class);
        return new ZenMetaInfoClient(api);
    }

    @Bean(name = IMAGE_SEARCH_CLIENT)
    @Lazy
    public ImageSearchClient imageSearchClient(@Value("${image_search.url}") String baseUrl,
                                               AsyncHttpClient asyncHttpClient) {
        FetcherSettings fetcherSettings = new FetcherSettings()
                .withFailFast(true)
                .withRequestRetries(2)
                .withSoftTimeout(Duration.ofSeconds(10))
                .withRequestTimeout(Duration.ofSeconds(20))
                .withParallel(5);
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        return new ImageSearchClient(baseUrl, fetcherFactory);
    }

    @Bean(name = ORGANIZATION_CLIENT)
    @Lazy
    public OrganizationsClient organizationsClient(AsyncHttpClient asyncHttpClient,
                                                   TvmIntegration tvmIntegration,
                                                   EnvironmentType environmentType,
                                                   PpcPropertiesSupport ppcPropertiesSupport) {
        DirectConfig config = directConfig.getBranch("organizations-api");
        String url = config.getString("url");
        FetcherSettings fetcherSettings = new FetcherSettings()
                .withFailFast(true)
                .withRequestRetries(config.findInt("request_retries").orElse(2))
                .withSoftTimeout(config.findDuration("soft_timeout").orElse(Duration.ofSeconds(1)))
                .withRequestTimeout(config.findDuration("request_timeout").orElse(Duration.ofSeconds(5)))
                .withParallel(config.findInt("parallel").orElse(1));
        //для поиска по тексту берём таймаут побольше и попыток поменьше
        FetcherSettings fetcherSettingsForSearchByText = new FetcherSettings()
                .withFailFast(true)
                .withRequestRetries(config.findInt("request_retries_text_search").orElse(2))
                .withSoftTimeout(config.findDuration("request_soft_timeout_text_search").orElse(Duration.ofSeconds(1)))
                .withRequestTimeout(config.findDuration("request_timeout_text_search").orElse(Duration.ofSeconds(5)));
        // Для пишущих операций отдельная настройка без softTimeout'а и параллелизма
        DirectConfig writeConfig = directConfig.getBranch("write");
        FetcherSettings writeRequestSettings = new FetcherSettings()
                .withFailFast(true)
                .withSoftTimeout(null)
                .withParallel(1)
                .withRequestRetries(writeConfig.findInt("request_retries").orElse(2))
                .withRequestTimeout(writeConfig.findDuration("request_timeout").orElse(Duration.ofSeconds(5)));
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        boolean isProd = environmentType.isProductionOrPrestable();
        int permalinkChunkSize = config.findInt("permalink_chunk_size").orElse(10);
        return new OrganizationsClient(url, fetcherFactory, tvmIntegration, fetcherSettingsForSearchByText,
                writeRequestSettings, ppcPropertiesSupport, isProd, permalinkChunkSize);
    }

    @Bean(name = TELEPHONY_CLIENT)
    @Lazy
    TelephonyClient telephonyClient(
            @Value("${telephony.url}") String url,
            @Value("${telephony.tvm_app_id}") int tvmAppId,
            PpcPropertiesSupport ppcPropertiesSupport,
            AsyncHttpClient asyncHttpClient,
            TvmIntegration tvmIntegration) {
        TvmService tvmService = TvmService.fromIdStrict(tvmAppId);
        ParallelFetcherFactory parallelFetcherFactory = new ParallelFetcherFactory(asyncHttpClient,
                new FetcherSettings());
        return new TelephonyClient(url, tvmIntegration, tvmService, parallelFetcherFactory,
                () -> ppcPropertiesSupport.get(TELEPHONY_PLAYBACK_ID).getOrDefault(""));
    }

    @Bean(name = YA_SMS_CLIENT_IMPL)
    @Lazy
    public YaSmsClientImpl yaSmsClientImpl(AsyncHttpClient asyncHttpClient,
                                           TvmIntegration tvmIntegration,
                                           EnvironmentType environmentType) {
        DirectConfig config = directConfig.getBranch("ya-sms-api");
        String url = config.getString("url");
        FetcherSettings fetcherSettings = new FetcherSettings()
                .withFailFast(true)
                .withRequestRetries(config.findInt("request_retries").orElse(0))
                .withSoftTimeout(config.findDuration("soft_timeout").orElse(Duration.ofSeconds(5)))
                .withRequestTimeout(config.findDuration("request_timeout").orElse(Duration.ofSeconds(5)));
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        boolean isProd = environmentType.isProductionOrPrestable();
        return new YaSmsClientImpl(url, fetcherFactory, tvmIntegration, isProd);
    }

    @Bean(name = YA_SMS_CLIENT_COMMON)
    @Lazy
    public YaSmsClient yaSmsClientCommon(YaSmsClientImpl yaSmsClientImpl) {
        return new YaSmsClientCommon(yaSmsClientImpl);
    }

    @Bean(name = YA_SMS_CLIENT_WEB)
    @Lazy
    public YaSmsClient yaSmsClientWeb(YaSmsClientImpl yaSmsClientImpl) {
        return new YaSmsClientWeb(yaSmsClientImpl);
    }

    @Lazy
    @Bean(name = CURATOR_FRAMEWORK_PROVIDER)
    public CuratorFrameworkProvider curatorFrameworkProvider(
            @Value("${zookeeper.servers}") String servers,
            @Value("${zookeeper.lock_path}") String lockPath
    ) {
        return new CuratorFrameworkProvider(servers, lockPath);
    }

    @Bean
    YandexSenderClient yandexSenderClient(DirectConfig config, AsyncHttpClient asyncHttpClient) {
        DirectConfig senderConf = config.getBranch("yandex_sender");
        Path tokenFile = FileUtils.expandHome(senderConf.getString("account_token_file"));

        YandexSenderConfig conf = new YandexSenderConfig(
                senderConf.getString("protocol"),
                senderConf.getString("host"),
                senderConf.getString("account_slug"),
                senderConf.findString("account_token")
                        .filter(accountToken -> !accountToken.isEmpty())
                        //если файл не найден, передаём фейковый токен
                        .orElseGet(() -> tokenFile.toFile().exists() ? FileUtils.slurp(tokenFile).trim() : "fake"));
        return new YandexSenderClient(conf, asyncHttpClient);
    }

    @Bean(name = XIVA_CLIENT)
    XivaClient xivaClient(DirectConfig config, AsyncHttpClient asyncHttpClient, TvmIntegration tvmIntegration) {
        DirectConfig clientConstantsConfig = config.getBranch("xiva_client");

        XivaConfig xivaConfig = new XivaConfig(
                clientConstantsConfig.getString("xiva.url"),
                clientConstantsConfig.getString("service")
        );

        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings());
        int tvmId = clientConstantsConfig.getInt("xiva.tvm_app_id");
        return new XivaClient(xivaConfig, fetcherFactory, tvmIntegration, TvmService.fromIdStrict(tvmId));
    }

    @Bean
    public ZoraFetcherSettings zoraFetcherSettings() {
        DirectConfig zoraFetcherConfig = directConfig.getBranch("zora_fetcher");

        return ZoraFetcherSettings.builder()
                .withParallel(zoraFetcherConfig.getInt("parallel"))
                .withConnectTimeout(zoraFetcherConfig.getDuration("connection_timeout"))
                .withGlobalTimeout(zoraFetcherConfig.getDuration("global_timeout"))
                .withRequestTimeout(zoraFetcherConfig.getDuration("request_timeout"))
                .withRequestRetries(zoraFetcherConfig.getInt("request_retries"))
                .withMetricRegistry(SolomonUtils.getParallelFetcherMetricRegistry(ZORA_FETCHER_METRIKA_LABEL))
                .build();
    }

    @Bean
    public ZoraFetcher zoraFetcher(AsyncHttpClient asyncHttpClient,
                                   @Qualifier(GO_ZORA_ASYNC_HTTP_CLIENT) AsyncHttpClient goZoraAsyncHttpClient,
                                   ZoraFetcherSettings settings) {
        return new ZoraFetcher(asyncHttpClient, goZoraAsyncHttpClient, settings);
    }

    @Bean
    public ZoraOnlineRequestCreator zoraOnlineRequestCreator() {
        DirectConfig zoraConfig = directConfig.getBranch("zora_online");

        return new ZoraOnlineRequestCreator(zoraConfig.getString("host"),
                zoraConfig.getInt("port"),
                zoraConfig.getString("source_name"));
    }

    @Bean
    public ZoraGoRequestCreator zoraGoRequestCreator() {
        DirectConfig zoraConfig = directConfig.getBranch("zora_go");

        return new ZoraGoRequestCreator(zoraConfig.getString("host"),
                zoraConfig.getInt("port"),
                zoraConfig.getString("source_name"));
    }

    @Bean
    public TakeoutClient takeoutClient(DirectConfig directConfig, TvmIntegration tvmIntegration,
                                       AsyncHttpClient asyncHttpClient) {
        String url = directConfig.getString("takeout_api.url");
        int tvmId = directConfig.getInt("takeout_api.tvm_app_id");
        int retries = directConfig.getInt("takeout_api.request_retries");
        ParallelFetcherFactory pff =
                new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings().withRequestRetries(retries));
        return new TakeoutClient(url, tvmIntegration, TvmService.fromIdStrict(tvmId), pff);
    }

    @Bean
    public CanvasToolsClient canvasToolsClient(DirectConfig directConfig, TvmIntegration tvmIntegration,
                                               AsyncHttpClient asyncHttpClient) {
        String url = directConfig.getString("canvas_api.url");
        int tvmId = directConfig.getInt("canvas_api.tvm_app_id");
        ParallelFetcherFactory pff = new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings());
        return new CanvasToolsClient(url, tvmIntegration, TvmService.fromIdStrict(tvmId), pff);
    }

    @Bean
    @Lazy
    public VideoClient videoClient(AsyncHttpClient asyncHttpClient) {
        final DirectConfig config = directConfig.getBranch("yandex-video");
        final Duration requestTimeout = config.findDuration("request_timeout")
                .orElse(Duration.ofSeconds(60));
        final String videoServiceBaseUrl = config.getString("url");
        int requestRetries = config.findInt("request_retries").orElse(2);
        Duration softTimeout = config.findDuration("soft_timeout").orElse(Duration.ofSeconds(15));

        return new VideoClient(asyncHttpClient,
                new VideoClientConfig(videoServiceBaseUrl, requestRetries, softTimeout, requestTimeout, 1));
    }

    @Bean
    @Lazy
    public CollectionsClient collectionsClient(AsyncHttpClient asyncHttpClient, TvmIntegration tvmIntegration) {
        DirectConfig config = directConfig.getBranch("collections-api");
        String baseUrl = config.getString("url");
        int tvmAppId = config.getInt("tvm_app_id");

        FetcherSettings fetcherSettings = new FetcherSettings()
                .withFailFast(true)
                .withRequestRetries(config.findInt("request_retries").orElse(2))
                .withSoftTimeout(config.findDuration("soft_timeout").orElse(Duration.ofSeconds(1)))
                .withRequestTimeout(config.findDuration("request_timeout").orElse(Duration.ofSeconds(5)));
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);

        return new CollectionsClient(baseUrl, fetcherFactory, tvmIntegration, TvmService.fromIdStrict(tvmAppId));
    }

    @Bean(name = YA_AUDIENCE_TOKEN_PROVIDER)
    @Lazy
    public LiveResource yaAudienceTokenProvider(
            @Value("${audience_client.audience_robot_token_path}") String tokenPath,
            @Qualifier(CONFIG_SCHEDULER_BEAN_NAME) TaskScheduler taskScheduler,
            LiveResourceFactoryBean liveResourceFactoryBean) {
        LiveResource liveResource = liveResourceFactoryBean.get(tokenPath);

        if (liveResource instanceof YavLiveResource) {
            return liveResource;
        }

        CachingLiveResource cachingLiveResource = new CachingLiveResource(liveResource);
        PollingLiveResourceWatcher resourceWatcher = new PollingLiveResourceWatcher(liveResource,
                liveResource.getContent(),
                taskScheduler, AUDIENCE_TOKEN_CHECK_RATE);
        resourceWatcher.addListener(cachingLiveResource);
        resourceWatcher.watch();
        return cachingLiveResource;
    }

    @Bean(name = YA_AUDIENCE_CLIENT)
    public YaAudienceClient yaAudienceClient(AsyncHttpClient asyncHttpClient,
                                             @Lazy @Qualifier(YA_AUDIENCE_TOKEN_PROVIDER)
                                                     LiveResource yaAudienceTokenProvider,
                                             @Value("${audience_client.base_audience_api_url}") String
                                                     baseAudienceApiUrl) {
        return new YaAudienceClient(asyncHttpClient, yaAudienceTokenProvider, baseAudienceApiUrl);
    }

    @Bean
    @Lazy
    public MediascopeClientConfig mediascopeClientConfig(DirectConfig directConfig) {
        var config = directConfig.getBranch("mediascope_client");
        return new MediascopeClientConfig(
                config.getString("oidc_url"),
                config.getString("api_url"),
                LiveResourceFactory.get(config.getString("client_id_path")).getContent(),
                LiveResourceFactory.get(config.getString("client_secret_path")).getContent());
    }

    @Bean
    @Lazy
    public MediascopeClient mediascopeClient(
            MediascopeClientConfig mediascopeClientConfig,
            AsyncHttpClient asyncHttpClient
    ) {
        return new MediascopeClient(asyncHttpClient, mediascopeClientConfig);
    }

    @Bean
    @Lazy
    public AppMetrikaClient appMetrikaClient(DirectConfig directConfig, TvmIntegration tvmIntegration,
                                             AsyncHttpClient asyncHttpClient) {
        String url = directConfig.getString("appmetrika.appmetrika_url");
        int tvmId = directConfig.getInt("appmetrika.tvm_app_id");
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings());
        return new AppMetrikaClient(url, tvmIntegration, TvmService.fromIdStrict(tvmId), fetcherFactory);
    }

    @Bean(name = UAAS_CLIENT)
    @Lazy
    public UaasClient uaasClient(DirectConfig directConfig, AsyncHttpClient asyncHttpClient) {
        var config =
                UaasClientConfig.builder()
                        .withUrl(directConfig.getString("uaas.url"))
                        .withConnectTimeout(directConfig.getDuration("uaas.connection_timeout"))
                        .withTimeout(directConfig.getDuration("uaas.request_timeout"))
                        .withRequestRetries(directConfig.getInt("uaas.request_retries"))
                        .build();
        return new UaasClient(config, asyncHttpClient);
    }

    @Bean
    @Lazy
    public BannerStorageClient bannerStorageClient(
            EnvironmentTypeProvider envProvider,
            DirectConfig directConfig,
            AsyncHttpClient asyncHttpClient
    ) {
        if (envProvider.get().isSandbox()) {
            // В песочнице не ходим в настоящий BannerStorage
            return new DummyBannerStorageClient();
        }
        var config = new BannerStorageClientConfiguration(
                directConfig.getString("bannerstorage_api.url"),
                directConfig.getString("bannerstorage_api.token"),
                directConfig.getString("bannerstorage_api.token_file_path")
        );
        return new RealBannerStorageClient(config, asyncHttpClient);
    }

    @Bean(BS_DOMAIN_ID_GENERATOR_CLIENT)
    public BsDomainIdGeneratorClient bsDomainIdGeneratorClient(
            DirectConfig directConfig,
            AsyncHttpClient asyncHttpClient,
            TvmIntegration tvmIntegration) {
        var clientConfig = new BsDomainIdGeneratorClientConfig.Builder()
                .withUrl(directConfig.getString("bs-id-generator.url"))
                .withConnectTimeout(directConfig.getDuration("bs-id-generator.connection_timeout"))
                .withRequestTimeout(directConfig.getDuration("bs-id-generator.request_timeout"))
                .withRequestRetries(directConfig.getInt("bs-id-generator.request_retries"))
                .withTvmId(directConfig.getInt("bs-id-generator.tvm_id"))
                .build();
        return new BsDomainIdGeneratorClient(clientConfig, asyncHttpClient, tvmIntegration);
    }

    @Bean
    @Lazy
    public LaasClient laasClient(AsyncHttpClient asyncHttpClient) {
        DirectConfig config = directConfig.getBranch("laas-api");
        String baseUrl = config.getString("url");

        FetcherSettings fetcherSettings = new FetcherSettings()
                .withFailFast(true)
                .withRequestRetries(config.findInt("request_retries").orElse(2))
                .withSoftTimeout(config.findDuration("soft_timeout").orElse(Duration.ofSeconds(1)))
                .withRequestTimeout(config.findDuration("request_timeout").orElse(Duration.ofSeconds(5)));
        ParallelFetcherFactory fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);

        return new LaasClient(baseUrl, fetcherFactory);
    }

    private RotorClient commonRotorClient(String configBranch, AsyncHttpClient asyncHttpClient,
                                          TvmIntegration tvmIntegration, String clientId) {
        DirectConfig rotorClientConfig = directConfig.getBranch(configBranch);

        String rotorUrl = rotorClientConfig.getString("url");
        String rotorSource = rotorClientConfig.getString("source");
        int tvmAppId = rotorClientConfig.getInt("tvm_zora_app_id");
        TvmService tvmService = TvmService.fromIdStrict(tvmAppId);

        FetcherSettings fetcherSettings = new FetcherSettings()
                .withParallel(rotorClientConfig.getInt("parallel"))
                .withRequestRetries(rotorClientConfig.getInt("request_retries"))
                .withSoftTimeout(rotorClientConfig.getDuration("soft_timeout"))
                .withRequestTimeout(rotorClientConfig.getDuration("request_timeout"))
                .withGlobalTimeout(rotorClientConfig.getDuration("global_timeout"));

        ParallelFetcherFactory parallelFetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);

        return new RotorClient(rotorSource, rotorUrl, parallelFetcherFactory, tvmIntegration, tvmService, clientId);
    }

    @Bean(name = ROTOR_CLIENT)
    @Lazy
    RotorClient rotorClient(AsyncHttpClient asyncHttpClient, TvmIntegration tvmIntegration) {
        return commonRotorClient("rotor_client", asyncHttpClient, tvmIntegration, null);
    }

    @Bean(name = ROTOR_TRACKING_URL_ANDROID_CLIENT)
    @Lazy
    RotorClient rotorTrackingUrlAndroidClient(AsyncHttpClient asyncHttpClient, TvmIntegration tvmIntegration) {
        return commonRotorClient("rotor_tracking_url_android", asyncHttpClient, tvmIntegration, "rmp");
    }

    @Bean(name = ROTOR_TRACKING_URL_IOS_CLIENT)
    @Lazy
    RotorClient rotorTrackingUrlIosClient(AsyncHttpClient asyncHttpClient, TvmIntegration tvmIntegration) {
        return commonRotorClient("rotor_tracking_url_ios", asyncHttpClient, tvmIntegration, "rmp");
    }

    @Bean
    @Lazy
    GeoSuggestClient geoSuggestClient(AsyncHttpClient asyncHttpClient,
                                      @Value("${geosuggest.api_url}") String apiUrl) {
        ParallelFetcherFactory parallelFetcherFactory = new ParallelFetcherFactory(
                asyncHttpClient,
                new FetcherSettings());
        return new GeoSuggestClient(parallelFetcherFactory, apiUrl);
    }

    @Bean(name = BANNER_CATEGORIES_MULTIK_CLIENT)
    @Lazy
    BannerCategoriesMultikClient bannerCategoriesMultikClient(AsyncHttpClient asyncHttpClient) {
        DirectConfig multikClientConfig = directConfig.getBranch("banner_categories_multik_client");

        String multikUrl = multikClientConfig.getString("url");
        ParallelFetcherFactory parallelFetcherFactory = new ParallelFetcherFactory(asyncHttpClient,
                new FetcherSettings());
        return new BannerCategoriesMultikClient(multikUrl, parallelFetcherFactory);
    }

    @Bean
    public LandlordClient landlordClient(AsyncHttpClient asyncHttpClient, TvmIntegration tvmIntegration) {
        var branch = directConfig.getBranch("landlord");
        var apiUrl = branch.getString("api_url");
        var tvmAppId = branch.getInt("tvm_app_id");
        var landingUrlSuffix = branch.getString("landing_url_suffix");
        var configuration = new LandlordConfiguration(apiUrl, tvmAppId, landingUrlSuffix);
        var fetcherSettings = new FetcherSettings()
                .withParallel(branch.getInt("parallel"))
                .withConnectTimeout(branch.getDuration("connection_timeout"))
                .withGlobalTimeout(branch.getDuration("global_timeout"))
                .withRequestTimeout(branch.getDuration("request_timeout"))
                .withRequestRetries(branch.getInt("request_retries"));
        var fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        return new LandlordClient(configuration, fetcherFactory, tvmIntegration);
    }

    @Bean
    public BvmClient bvmClient(AsyncHttpClient asyncHttpClient) {
        var branch = directConfig.getBranch("bvm");
        var apiUrl = branch.getString("api_url");
        var configuration = new BvmConfiguration(apiUrl);
        var fetcherSettings = new FetcherSettings()
                .withParallel(branch.getInt("parallel"))
                .withConnectTimeout(branch.getDuration("connection_timeout"))
                .withGlobalTimeout(branch.getDuration("global_timeout"))
                .withRequestTimeout(branch.getDuration("request_timeout"))
                .withRequestRetries(branch.getInt("request_retries"));
        var fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        return new BvmClient(configuration, fetcherFactory);
    }

    @Bean(BMAPI_CLIENT)
    public BmapiClient bmapiClient(AsyncHttpClient asyncHttpClient) {
        var branch = directConfig.getBranch("bmapi");
        var apiUrl = branch.getString("api_url");
        var configuration = new BmapiConfiguration(apiUrl);
        var fetcherSettings = new FetcherSettings()
                .withParallel(branch.getInt("parallel"))
                .withConnectTimeout(branch.getDuration("connection_timeout"))
                .withRequestTimeout(branch.getDuration("request_timeout"))
                .withRequestRetries(branch.getInt("request_retries"));
        var fetcherFactory = new ParallelFetcherFactory(asyncHttpClient, fetcherSettings);
        return new BmapiClient(configuration, fetcherFactory);
    }

    @Bean(TELEGRAM_CLIENT_DIRECT_FEATURE)
    @Lazy
    public TelegramClient telegramClient(AsyncHttpClient asyncHttpClient,
                                         LiveResourceFactoryBean liveResourceFactoryBean) {
        var tokenPath = directConfig.getString("telegram.direct-feature.token");
        var token = liveResourceFactoryBean.get(tokenPath);
        return new TelegramClient(asyncHttpClient, token);
    }

    @Bean(PRODUCTION_INTAPI_SMART_CLIENT)
    @Lazy
    public IntapiSmartClient productionIntapiSmartClient(
            @Value("${direct_production_intapi.url}") String url,
            @Value("${direct_production_intapi.tvm_app_id}") int tvmAppId,
            AsyncHttpClient asyncHttpClient,
            TvmIntegration tvmIntegration) {
        var parallelFetcherFactory = new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings());
        return new IntapiSmartClient(parallelFetcherFactory, tvmIntegration, TvmService.fromIdStrict(tvmAppId), url);
    }

    @Bean(ANTIFRAUD_CLIENT)
    @Lazy
    public AntifraudClient antifraudClient(
            EnvironmentType environmentType,
            AsyncHttpClient asyncHttpClient,
            TvmIntegration tvmIntegration) {
        var config = directConfig.getBranch("antifraud_api");
        var baseUrl = config.getString("host");

        var parallelFetcherFactory = new ParallelFetcherFactory(asyncHttpClient, new FetcherSettings());
        boolean isProd = environmentType.isProduction();
        return new AntifraudClient(parallelFetcherFactory, tvmIntegration, baseUrl, isProd);
    }

}
