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

import java.util.List;

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

import ru.yandex.direct.api.v5.result.ApiMassResult;
import ru.yandex.direct.api.v5.result.ApiResult;
import ru.yandex.direct.api.v5.result.ApiResultState;
import ru.yandex.direct.api.v5.security.ApiAuthenticationSource;
import ru.yandex.direct.api.v5.validation.DefectType;
import ru.yandex.direct.common.db.PpcPropertiesSupport;
import ru.yandex.direct.core.entity.feature.service.FeatureService;
import ru.yandex.direct.validation.result.PathConverter;
import ru.yandex.direct.validation.result.ValidationResult;

import static com.google.common.base.Preconditions.checkState;
import static java.util.Collections.emptyList;
import static ru.yandex.direct.api.v5.entity.OperationOnListUtils.unionValidateAndProcessResult;

/**
 * Делегат, внутреннее представление запроса в котором это список, а внутреннее представление ответа -- список
 * результатов {@link ApiResult}. Обычно подходит для методов add, update, delete, выполняющих некоторое действие
 * над набором входных элементов. Знание структуры внутренних запроса и ответа позволяет этому делегату
 * валидировать каждый элемент списка в запросе, и, в случае ошибки, выдавать результат для этого элемента без
 * вызова основной операции {@link #processList(List, C)}.
 *
 * @param <Req>       внешенее представление запроса (из wsdl)
 * @param <Resp>      внешнее представление ответа (из wsdl)
 * @param <IntReqEl>  элемент списка представляющего внутреннее представление запроса
 * @param <IntRespEl> параметр элемента {@link ApiResult} в результирующем списке
 * @param <C>         контейнер
 */
@ParametersAreNonnullByDefault
public abstract class OperationOnListDelegateWithContainer<Req, Resp, IntReqEl, IntRespEl, C>
        extends BaseApiServiceDelegate<Req, Resp, List<IntReqEl>, ApiResult<IntRespEl>> {

    public OperationOnListDelegateWithContainer(PathConverter apiPathConverter,
                                                ApiAuthenticationSource apiAuthenticationSource,
                                                PpcPropertiesSupport ppcPropertiesSupport,
                                                @Nullable FeatureService featureService) {
        super(apiPathConverter, apiAuthenticationSource);
    }

    public OperationOnListDelegateWithContainer(PathConverter apiPathConverter,
                                                ApiAuthenticationSource apiAuthenticationSource) {
        this(apiPathConverter, apiAuthenticationSource, null, null);
    }

    @Override
    public final ApiResult<List<ApiResult<IntRespEl>>> processRequest(List<IntReqEl> internalRequest) {
        C container = createContainer();
        ValidationResult<List<IntReqEl>, DefectType> validationResult =
                validateInternalRequest(internalRequest, container);
        List<IntReqEl> validItems = ValidationResult.getValidItems(validationResult);
        int invalidsNum = internalRequest.size() - validItems.size();
        checkState(invalidsNum >= 0);

        ApiMassResult<IntRespEl> resultForValidItems;
        if (!validItems.isEmpty()) {
            resultForValidItems = processList(validItems, container);
        } else {
            // для пустого списка валидных элементов -- пустой результат
            resultForValidItems = new ApiMassResult<>(emptyList(), emptyList(), emptyList(), ApiResultState.SUCCESSFUL);
        }
        if (!resultForValidItems.isSuccessful()) {
            // ошибка операции -- можно сразу возвращать
            return resultForValidItems;
        }
        checkState(resultForValidItems.getResult().size() == validItems.size(),
                "Должен быть возвращен результат для каждого входного элемента");

        // объединяем результаты валидации с результатами, полученными из операции
        List<ApiResult<IntRespEl>> results =
                unionValidateAndProcessResult(validationResult, resultForValidItems, internalRequest);
        return new ApiMassResult<>(results, emptyList(), emptyList(), ApiResultState.SUCCESSFUL)
                .withCounts(resultForValidItems.getSuccessfulObjectsCount(),
                        resultForValidItems.getUnsuccessfulObjectsCount() + invalidsNum);
    }

    protected abstract C createContainer();

    /**
     * Валидация внутреннего представления запроса. Невалидные элементы не будут переданы в
     * {@link #processList(List, C)}.
     * Если после валидации в списке не останется валидных элементов, то {@link #processList(List, C)} вообще не будет
     * выполнен.
     */
    @Nonnull
    public ValidationResult<List<IntReqEl>, DefectType> validateInternalRequest(
            List<IntReqEl> internalRequest,
            C container) {
        return new ValidationResult<>(internalRequest);
    }

    /**
     * Выполнить операцию над набором корректных входных элементов
     * <p>
     * Внимание! Если валидация списка элементов {@link #validateInternalRequest(List, C)} найдёт ошибку
     * в каждом элементе списка, то этот метод {@code processList} не будет вызван вообще.
     *
     * @param validItems список валидных элементов
     * @return результат {@link ApiResult} операции надо списком
     */
    @SuppressWarnings("WeakerAccess")
    public abstract ApiMassResult<IntRespEl> processList(
            List<IntReqEl> validItems,
            C container);
}
