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

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.function.Function;

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

import com.yandex.direct.api.v5.bids.BidActionResult;
import com.yandex.direct.api.v5.bids.BidSetAutoItem;
import com.yandex.direct.api.v5.bids.BidSetItem;
import com.yandex.direct.api.v5.bids.CalculateByEnum;
import com.yandex.direct.api.v5.bids.SetAutoRequest;
import com.yandex.direct.api.v5.bids.SetAutoResponse;
import com.yandex.direct.api.v5.bids.SetRequest;
import com.yandex.direct.api.v5.bids.SetResponse;
import com.yandex.direct.api.v5.general.PositionEnum;
import com.yandex.direct.api.v5.general.ScopeEnum;
import one.util.streamex.StreamEx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ru.yandex.direct.api.v5.converter.ResultConverter;
import ru.yandex.direct.api.v5.result.ApiResult;
import ru.yandex.direct.core.entity.bids.container.BidTargetType;
import ru.yandex.direct.core.entity.bids.container.SetAutoBidCalculationType;
import ru.yandex.direct.core.entity.bids.container.SetAutoBidItem;
import ru.yandex.direct.core.entity.bids.container.SetAutoNetworkByCoverage;
import ru.yandex.direct.core.entity.bids.container.SetAutoSearchByPosition;
import ru.yandex.direct.core.entity.bids.container.SetAutoSearchByTrafficVolume;
import ru.yandex.direct.core.entity.bids.container.SetBidItem;
import ru.yandex.direct.core.entity.bids.container.ShowConditionType;
import ru.yandex.direct.core.entity.keyword.model.Place;
import ru.yandex.direct.validation.result.PathConverter;

import static java.util.Collections.emptyList;
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.utils.FunctionalUtils.mapList;

@Component
@ParametersAreNonnullByDefault
public class BidsHelperConverter {
    private final ResultConverter resultConverter;

    @Autowired
    public BidsHelperConverter(ResultConverter resultConverter) {
        this.resultConverter = resultConverter;
    }

    @Nonnull
    private static EnumSet<BidTargetType> convertScope(List<ScopeEnum> scopes) {
        return EnumSet.copyOf(StreamEx.of(scopes)
                .map(BidsHelperConverter::convertSingleScope)
                .nonNull()
                .toList());
    }

    @Nullable
    private static BidTargetType convertSingleScope(@Nullable ScopeEnum externalScope) {
        if (externalScope == null) {
            return null;
        }
        switch (externalScope) {
            case SEARCH:
                return BidTargetType.SEARCH;
            case NETWORK:
                return BidTargetType.CONTEXT;
            default:
                throw new IllegalArgumentException(String.format("Unknown scope enum: %s.", externalScope));
        }
    }

    @Nullable
    private static SetAutoBidCalculationType convertCalculationMode(@Nullable CalculateByEnum calculateBy) {
        if (calculateBy == null) {
            return null;
        }
        switch (calculateBy) {
            case DIFF:
                return SetAutoBidCalculationType.DIFF;
            case VALUE:
                return SetAutoBidCalculationType.VALUE;
            default:
                throw new IllegalArgumentException(String.format("Unknown calculateBy enum: %s.", calculateBy));
        }
    }

    @Nullable
    private static Place convertPosition(@Nullable PositionEnum position) {
        if (position == null) {
            return null;
        }
        switch (position) {
            case P_11:
            case PREMIUMFIRST:
                return Place.PREMIUM1;
            case P_12:
                return Place.PREMIUM2;
            case P_13:
                return Place.PREMIUM3;
            case P_14:
            case PREMIUMBLOCK:
                return Place.PREMIUM4;
            // Да, всё, кроме FOOTERFIRST мапится на первую позицию Гарантии
            case P_21:
            case FOOTERFIRST:
                return Place.GUARANTEE1;
            case P_22:
                return Place.GUARANTEE1;
            case P_23:
                return Place.GUARANTEE1;
            case P_24:
            case FOOTERBLOCK:
                return Place.GUARANTEE4;
            default:
                throw new IllegalArgumentException(String.format("Unknown position enum: %s.", position));
        }
    }

    /**
     * @return преобразованный внешний запрос {@link SetAutoRequest}
     * в список {@link SetAutoBidItem} - core-представление запроса на изменение ставок
     */
    @Nonnull
    public List<SetAutoBidItem> convertFromSetAutoRequest(SetAutoRequest request) {
        if (request.getBids() == null) {
            return emptyList();
        }
        return StreamEx.of(request.getBids())
                .map(this::convertSetAutoItem)
                .toList();
    }

    /**
     * @return внутренний {@link ApiResult}, преобразованный во внешнее представление {@link SetAutoResponse}
     */
    @Nonnull
    public SetAutoResponse convertToSetAutoResponse(ApiResult<List<ApiResult<SetAutoBidItem>>> result,
                                                    PathConverter pathConverter) {
        List<BidActionResult> results =
                StreamEx.of(result.getResult())
                        .mapToEntry(r -> resultConverter.convertToGenericActionResult(r, pathConverter))
                        .mapKeyValue((r, actionResult) -> {
                            BidActionResult rr = new BidActionResult();
                            if (r.isSuccessful()) {
                                SetAutoBidItem bidItem = r.getResult();
                                rr.withKeywordId(bidItem.getId())
                                        .withAdGroupId(bidItem.getAdGroupId())
                                        .withCampaignId(bidItem.getCampaignId());
                            }
                            // getErrors() и getWarnings() под капотом лениво инициализируют списки ошибок и предупреждений
                            // проверяем списки на наличие элементов, чтобы не отдавать "Errors" и "Warnings", если ошибок не было
                            // See also ResultConverter.toBidActionResult()
                            if (!actionResult.getErrors().isEmpty()) {
                                rr.setErrors(actionResult.getErrors());
                            }
                            if (!actionResult.getWarnings().isEmpty()) {
                                rr.setWarnings(actionResult.getWarnings());
                            }
                            return rr;
                        })
                        .toList();
        return new SetAutoResponse()
                .withSetAutoResults(results);
    }

    @Nonnull
    public SetResponse convertSetBidsResponse(ApiResult<List<ApiResult<SetBidItem>>> result,
                                              PathConverter pathConverter) {
        List<BidActionResult> actionResults = StreamEx.of(result)
                .map(rlist -> StreamEx.of(rlist.getResult())
                        .map(r -> resultConverter.convertToBidActionResult(r, pathConverter))
                        .toList())
                .toFlatList(Function.identity());

        return new SetResponse().withSetResults(actionResults);
    }

    /**
     * Конвертирует запрос из объектного представления JSON'а во внутренние сущности-контейнеры
     *
     * @param setBidsRequest Запрос, пришедший от пользователя
     * @return Набор бидов, содержащих данные запроса польлзователя
     */
    @Nonnull
    public List<SetBidItem> convertSetBidsRequest(SetRequest setBidsRequest) {
        if (setBidsRequest.getBids() == null) {
            return new ArrayList<>();
        }
        return mapList(setBidsRequest.getBids(), this::convertSetBidsItem);
    }

    @Nonnull
    private SetAutoBidItem convertSetAutoItem(BidSetAutoItem externalItem) {
        SetAutoBidItem result = new SetAutoBidItem()
                .withId(externalItem.getKeywordId())
                .withAdGroupId(externalItem.getAdGroupId())
                .withCampaignId(externalItem.getCampaignId())
                .withScope(convertScope(externalItem.getScope()));
        // заполняем пришедшие поля безусловно
        // имеет смысл, чтобы ругаться на ContextCoverage<0, когда Scope не содержит CONTEXT (тест ru.yandex.autotests.direct.api.bids.setauto.SetAutoNegativeTest#trySetAutoWithContextCoverageLess0)
        BigDecimal maxBid = convertToDbPrice(externalItem.getMaxBid());
        result.withSearchByPosition(new SetAutoSearchByPosition()
                .withPosition(convertPosition(externalItem.getPosition()))
                .withIncreasePercent(externalItem.getIncreasePercent())
                .withCalculatedBy(convertCalculationMode(externalItem.getCalculateBy()))
                .withMaxBid(maxBid)
        );
        result.withNetworkByCoverage(new SetAutoNetworkByCoverage()
                .withContextCoverage(externalItem.getContextCoverage())
                .withIncreasePercent(externalItem.getIncreasePercent())
                .withMaxBid(maxBid)
        );
        result.withSearchByTrafficVolume(new SetAutoSearchByTrafficVolume());

        return result;
    }

    @Nullable
    private SetBidItem convertSetBidsItem(@Nullable BidSetItem item) {
        if (item == null) {
            return null;
        }

        return new SetBidItem()
                .withShowConditionType(ShowConditionType.KEYWORD)
                .withId(item.getKeywordId())
                .withAdGroupId(item.getAdGroupId())
                .withCampaignId(item.getCampaignId())
                .withPriceSearch(convertToDbPrice(item.getBid()))
                .withPriceContext(convertToDbPrice(item.getContextBid()))
                .withAutobudgetPriority(convertStrategyPriority(item.getStrategyPriority()));
    }
}
