package ru.yandex.direct.api.v5.entity.keywords.converter;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

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

import com.google.common.collect.ImmutableSet;
import com.yandex.direct.api.v5.general.PriorityEnum;
import com.yandex.direct.api.v5.keywords.AddRequest;
import com.yandex.direct.api.v5.keywords.KeywordAddItem;
import one.util.streamex.StreamEx;
import org.springframework.stereotype.Component;

import ru.yandex.direct.api.v5.entity.keywords.container.AddInputItem;
import ru.yandex.direct.core.entity.adgroup.model.AdGroupSimple;
import ru.yandex.direct.core.entity.adgroup.model.AdGroupType;
import ru.yandex.direct.core.entity.adgroup.service.AdGroupService;
import ru.yandex.direct.core.entity.campaign.model.DbStrategy;
import ru.yandex.direct.core.entity.client.model.Client;
import ru.yandex.direct.core.entity.client.service.ClientService;
import ru.yandex.direct.core.entity.feature.service.FeatureService;
import ru.yandex.direct.core.entity.keyword.model.Keyword;
import ru.yandex.direct.core.entity.relevancematch.model.RelevanceMatch;
import ru.yandex.direct.currency.Currency;
import ru.yandex.direct.dbutil.model.ClientId;

import static com.google.common.base.Preconditions.checkNotNull;
import static ru.yandex.direct.api.v5.common.ConverterUtils.convertStrategyPriority;
import static ru.yandex.direct.api.v5.common.ConverterUtils.convertToDbPrice;
import static ru.yandex.direct.api.v5.common.RelevanceMatchCategoriesConverter.autotargetingCategoriesToCore;
import static ru.yandex.direct.core.entity.keyword.service.KeywordUtils.hasAutotargetingPrefix;
import static ru.yandex.direct.core.entity.keyword.service.KeywordUtils.phraseWithoutAutotargetingPrefix;

@Component
@ParametersAreNonnullByDefault
public class KeywordsAddRequestConverter {
    static final String AUTOTARGETING = "---autotargeting";
    static final String ESCAPED_AUTOTARGETING = "'---autotargeting";
    private static final Set<AdGroupType> CPM_GROUP_TYPES =
            ImmutableSet.of(AdGroupType.CPM_BANNER, AdGroupType.CPM_VIDEO);

    private final ClientService clientService;
    private final AdGroupService adGroupService;
    private final FeatureService featureService;

    KeywordsAddRequestConverter(ClientService clientService, AdGroupService adGroupService,
                                FeatureService featureService) {
        this.clientService = clientService;
        this.adGroupService = adGroupService;
        this.featureService = featureService;
    }

    public List<AddInputItem> convertRequest(AddRequest request, ClientId clientId) {
        Client client = checkNotNull(clientService.getClient(clientId));
        Currency clientCurrency = client.getWorkCurrency().getCurrency();
        BigDecimal keywordsDefaultPrice = clientCurrency.getMinPrice();
        BigDecimal keywordsDefaultCpmPrice = clientCurrency.getMinCpmPrice();

        Set<Long> referencedAdGroupIds = getReferencedAdGroupIds(request);
        Map<Long, AdGroupSimple> adGroupSimpleMap = adGroupService.getSimpleAdGroups(clientId, referencedAdGroupIds);

        Map<Long, DbStrategy> adGroupToCampaignStrategy =
                adGroupService.getStrategiesByAdGroupIds(clientId, referencedAdGroupIds);
        Map<Long, BigDecimal> adGroupToRelevanceMatchDefaultPrice = adGroupService
                .getRelevanceMatchDefaultPricesByAdGroupIds(clientId, referencedAdGroupIds, clientCurrency);
        Map<Long, BigDecimal> adGroupToDefaultPrice = StreamEx.of(referencedAdGroupIds)
                .mapToEntry(adGroupSimpleMap::get)
                .nonNullValues()
                .mapValues(AdGroupSimple::getType)
                .mapValues(CPM_GROUP_TYPES::contains)
                .mapValues(isCpmGroup -> isCpmGroup ? keywordsDefaultCpmPrice : keywordsDefaultPrice)
                .toMap();
        Map<Long, AdGroupType> adGroupTypeMap = StreamEx.of(referencedAdGroupIds)
                .mapToEntry(adGroupSimpleMap::get)
                .nonNullValues()
                .mapValues(AdGroupSimple::getType)
                .toMap();

        Set<Long> relevanceMatchSupportsContextPriceByGroups = StreamEx.of(referencedAdGroupIds)
                .mapToEntry(adGroupSimpleMap::get)
                .nonNullValues()
                .mapValues(AdGroupSimple::getType)
                .filterValues(
                        groupType -> groupType == AdGroupType.MOBILE_CONTENT)
                .keys()
                .toSet();

        return convertRequest(request, adGroupToRelevanceMatchDefaultPrice, adGroupToCampaignStrategy,
                adGroupToDefaultPrice, relevanceMatchSupportsContextPriceByGroups, adGroupTypeMap);
    }

    public List<AddInputItem> convertRequest(AddRequest request,
                                             Map<Long, BigDecimal> adGroupToRelevanceMatchDefaultPrice,
                                             Map<Long, DbStrategy> adGroupToCampaignStrategy,
                                             Map<Long, BigDecimal> adGroupToDefaultPrice,
                                             Set<Long> relevanceMatchSupportsContextPriceByGroups,
                                             Map<Long, AdGroupType> adGroupTypeMap) {
        return request.getKeywords().stream()
                .map(item -> {
                    Long adGroupId = item.getAdGroupId();
                    DbStrategy strategy = adGroupToCampaignStrategy.get(adGroupId);
                    BigDecimal relevanceMatchDefaultPrice = adGroupToRelevanceMatchDefaultPrice.get(adGroupId);
                    BigDecimal defaultPrice = adGroupToDefaultPrice.get(adGroupId);
                    AdGroupType adGroupType = adGroupTypeMap.get(adGroupId);
                    return convertItem(item, strategy, relevanceMatchDefaultPrice,
                            defaultPrice, relevanceMatchSupportsContextPriceByGroups.contains(adGroupId), adGroupType);
                })
                .collect(Collectors.toList());
    }

    private AddInputItem convertItem(KeywordAddItem item, @Nullable DbStrategy strategy,
                                     @Nullable BigDecimal relevanceMatchDefaultPrice, BigDecimal keywordsDefaultPrice,
                                     boolean relevanceMatchSupportsContextPrice, AdGroupType adGroupType) {
        if (item.getKeyword() != null && (item.getKeyword().equals(AUTOTARGETING) || item.getKeyword()
                .equals(ESCAPED_AUTOTARGETING))) {
            return convertRelevanceMatchItem(item, strategy, relevanceMatchDefaultPrice,
                    relevanceMatchSupportsContextPrice, adGroupType);
        } else {
            return convertKeywordItem(item, strategy, keywordsDefaultPrice);
        }
    }

    AddInputItem convertKeywordItem(KeywordAddItem item, @Nullable DbStrategy strategy, BigDecimal minPrice) {
        Keyword keyword = new Keyword()
                .withAdGroupId(item.getAdGroupId())
                .withPhrase(phraseWithoutAutotargetingPrefix(item.getKeyword()))
                .withIsAutotargeting(hasAutotargetingPrefix(item.getKeyword()))
                .withPrice(convertToDbPrice(item.getBid()))
                .withPriceContext(convertToDbPrice(item.getContextBid()))
                .withAutobudgetPriority(convertStrategyPriority(item.getStrategyPriority()))
                .withHrefParam1(trim(item.getUserParam1()))
                .withHrefParam2(trim(item.getUserParam2()));

        if (keyword.getPrice() == null && strategy != null && !strategy.isAutoBudget() && !strategy.isSearchStop()) {
            keyword.setPrice(minPrice);
        }
        if (keyword.getPriceContext() == null && strategy != null && strategy.isDifferentPlaces()
                && !strategy.isAutoBudget() && !strategy.isNetStop()) {
            keyword.setPriceContext(minPrice);
        }
        boolean priorityIsSetForManualStrategy = false;
        if (keyword.getAutobudgetPriority() == null) {
            keyword.setAutobudgetPriority(convertStrategyPriority(PriorityEnum.NORMAL));
        } else {
            if (strategy != null && !strategy.isAutoBudget()) {
                priorityIsSetForManualStrategy = true;
            }
        }
        return AddInputItem.createItemForKeyword(keyword, priorityIsSetForManualStrategy);
    }

    AddInputItem convertRelevanceMatchItem(KeywordAddItem item, @Nullable DbStrategy strategy,
                                           @Nullable BigDecimal relevanceMatchDefaultPrice,
                                           boolean relevanceMatchSupportsContextPrice,
                                           AdGroupType adGroupType) {
        RelevanceMatch relevanceMatch = new RelevanceMatch()
                .withAdGroupId(item.getAdGroupId())
                .withPrice(convertToDbPrice(item.getBid()))
                .withPriceContext(convertToDbPrice(item.getContextBid()))
                .withAutobudgetPriority(convertStrategyPriority(item.getStrategyPriority()))
                .withHrefParam1(trim(item.getUserParam1()))
                .withHrefParam2(trim(item.getUserParam2()))
                .withRelevanceMatchCategories(autotargetingCategoriesToCore(item.getAutotargetingCategories(),
                        adGroupType));

        if (relevanceMatch.getPrice() == null && relevanceMatchDefaultPrice != null
                && strategy != null && !strategy.isAutoBudget() && !strategy.isSearchStop()) {
            relevanceMatch.setPrice(relevanceMatchDefaultPrice);
        }
        if (relevanceMatchSupportsContextPrice && relevanceMatch.getPriceContext() == null && strategy != null
                && strategy.isDifferentPlaces() && !strategy.isAutoBudget() && !strategy.isNetStop()) {
            relevanceMatch.setPriceContext(relevanceMatchDefaultPrice);
        }

        boolean priorityIsSetForManualStrategy = false;
        if (relevanceMatch.getAutobudgetPriority() == null) {
            relevanceMatch.setAutobudgetPriority(convertStrategyPriority(PriorityEnum.NORMAL));
        } else {
            if (strategy != null && !strategy.isAutoBudget()) {
                priorityIsSetForManualStrategy = true;
            }
        }
        return AddInputItem.createItemForRelevanceMatch(relevanceMatch, priorityIsSetForManualStrategy);
    }

    private Set<Long> getReferencedAdGroupIds(AddRequest request) {
        return request.getKeywords().stream()
                .map(KeywordAddItem::getAdGroupId)
                .filter(Objects::nonNull)
                .filter(id -> id > 0)
                .collect(Collectors.toSet());
    }

    private static String trim(@Nullable String string) {
        return string != null ? string.trim() : null;
    }
}
