package ru.yandex.direct.api.v5.entity.feeds.delegate;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;

import com.yandex.direct.api.v5.feeds.FeedGetItem;
import com.yandex.direct.api.v5.feeds.FeedsSelectionCriteria;
import com.yandex.direct.api.v5.feeds.GetRequest;
import com.yandex.direct.api.v5.feeds.GetResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ru.yandex.direct.api.v5.common.ApiPathConverter;
import ru.yandex.direct.api.v5.common.validation.GetRequestGeneralValidator;
import ru.yandex.direct.api.v5.entity.GenericGetRequest;
import ru.yandex.direct.api.v5.entity.GetApiServiceDelegate;
import ru.yandex.direct.api.v5.entity.feeds.container.FeedGetContainer;
import ru.yandex.direct.api.v5.entity.feeds.converter.GetResponseConverterService;
import ru.yandex.direct.api.v5.entity.feeds.validation.FeedsValidationService;
import ru.yandex.direct.api.v5.security.ApiAuthenticationSource;
import ru.yandex.direct.api.v5.validation.DefectType;
import ru.yandex.direct.core.entity.campaign.repository.CampaignRepository;
import ru.yandex.direct.core.entity.feed.container.FeedQueryFilter;
import ru.yandex.direct.core.entity.feed.model.Feed;
import ru.yandex.direct.core.entity.feed.repository.FeedRepository;
import ru.yandex.direct.dbutil.model.ClientId;
import ru.yandex.direct.dbutil.sharding.ShardHelper;
import ru.yandex.direct.validation.builder.ItemValidationBuilder;
import ru.yandex.direct.validation.builder.When;
import ru.yandex.direct.validation.result.ValidationResult;

import static com.yandex.direct.api.v5.adgroups.GetRequest.PropInfo.SELECTION_CRITERIA;
import static java.util.Collections.emptyMap;
import static java.util.stream.Collectors.toSet;
import static ru.yandex.direct.utils.CommonUtils.ifNotNull;
import static ru.yandex.direct.utils.FunctionalUtils.mapList;

@Component
@ParametersAreNonnullByDefault
public class GetFeedsDelegate extends GetApiServiceDelegate<GetRequest, GetResponse, FeedAnyFieldEnum,
        FeedsSelectionCriteria, FeedGetContainer> {

    private final GetResponseConverterService getResponseConverterService;
    private final ShardHelper shardHelper;
    private final FeedRepository feedRepository;
    private final CampaignRepository campaignRepository;

    @Autowired
    public GetFeedsDelegate(
            ApiAuthenticationSource auth,
            GetResponseConverterService getResponseConverterService,
            ShardHelper shardHelper,
            FeedRepository feedRepository,
            CampaignRepository campaignRepository) {
        super(ApiPathConverter.forFeeds(), auth);
        this.getResponseConverterService = getResponseConverterService;
        this.shardHelper = shardHelper;
        this.feedRepository = feedRepository;
        this.campaignRepository = campaignRepository;
    }

    @Nullable
    @Override
    public ValidationResult<GetRequest, DefectType> validateRequest(GetRequest externalRequest) {
        ItemValidationBuilder<GetRequest, DefectType> vb = ItemValidationBuilder.of(externalRequest);
        vb.item(externalRequest.getSelectionCriteria(), SELECTION_CRITERIA.schemaName.toString())
                .checkBy(FeedsValidationService::validateSelectionCriteria, When.notNull());
        vb.checkBy(GetRequestGeneralValidator::validateRequestWithDefectTypes, When.notNull());
        return vb.getResult();
    }

    @Override
    public Set<FeedAnyFieldEnum> extractFieldNames(GetRequest externalRequest) {
        return Stream.of(
                externalRequest.getFieldNames().stream().map(FeedAnyFieldEnum::fromFeedFieldEnum),
                externalRequest.getFileFeedFieldNames().stream().map(FeedAnyFieldEnum::fromFileFeedFieldEnum),
                externalRequest.getUrlFeedFieldNames().stream().map(FeedAnyFieldEnum::fromUrlFeedFieldEnum)
        ).flatMap(Function.identity()).collect(toSet());
    }

    @Override
    @Nullable
    public FeedsSelectionCriteria extractSelectionCriteria(GetRequest externalRequest) {
        return externalRequest.getSelectionCriteria();
    }

    @Override
    public List<FeedGetContainer> get(GenericGetRequest<FeedAnyFieldEnum, FeedsSelectionCriteria> request) {
        ClientId clientId = auth.getChiefSubclient().getClientId();
        FeedsSelectionCriteria selectionCriteria = request.getSelectionCriteria();

        int shard = shardHelper.getShardByClientId(clientId);
        List<Long> ids = ifNotNull(selectionCriteria, FeedsSelectionCriteria::getIds);
        FeedQueryFilter filter = FeedQueryFilter.newBuilder()
                .withFeedIds(ids)
                .withLimitOffset(request.getLimitOffset())
                .build();
        List<Feed> feeds = feedRepository.get(shard, clientId, filter);

        Map<Long, List<Long>> campaignIdsByFeedId;
        if (request.getRequestedFields().contains(FeedAnyFieldEnum.CAMPAIGN_IDS)) {
            List<Long> feedIds = mapList(feeds, Feed::getId);
            campaignIdsByFeedId = campaignRepository.getCampaignIdsByFeedId(shard, feedIds);
        } else {
            campaignIdsByFeedId = emptyMap();
        }

        return mapList(feeds, f -> new FeedGetContainer(f, campaignIdsByFeedId.get(f.getId())));
    }

    @Override
    public GetResponse convertGetResponse(
            List<FeedGetContainer> feedGetContainers,
            Set<FeedAnyFieldEnum> requestedFields,
            @Nullable Long limitedBy
    ) {
        GetResponse response = new GetResponse().withLimitedBy(limitedBy);
        if (!feedGetContainers.isEmpty()) {
            List<FeedGetItem> getItems = mapList(feedGetContainers, getResponseConverterService::convertFeed);
            getResponseConverterService.filterProperties(getItems, requestedFields);
            response.withFeeds(getItems);
        }
        return response;
    }

}
