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

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import ru.yandex.direct.api.v5.validation.DefectType;
import ru.yandex.direct.result.OperationMeta;
import ru.yandex.direct.validation.result.DefectInfo;
import ru.yandex.direct.validation.result.PathNode;
import ru.yandex.direct.validation.result.ValidationResult;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static ru.yandex.direct.utils.FunctionalUtils.mapList;
import static ru.yandex.direct.validation.result.PathHelper.index;
import static ru.yandex.direct.validation.result.PathHelper.path;

public class ApiMassResult<R> extends ApiResult<List<ApiResult<R>>> {

    private OperationMeta operationMeta;
    private int successfulCount;
    private int errorCount;

    public ApiMassResult(List<ApiResult<R>> results, List<DefectInfo<DefectType>> errors,
                         List<DefectInfo<DefectType>> warnings, ApiResultState state) {
        super(results, errors, warnings, state);

        checkArgument(state != ApiResultState.CANCELED, "MassResult can not be in canceled state");
        if (results != null) {
            // Считаем, что после создания не меняются ни elementsResults, ни massValidation, и count'ы можно запомнить
            this.successfulCount = (int) results.stream().filter(ApiResult::isSuccessful).count();
            this.errorCount = results.size() - successfulCount;
        }
    }

    public int getSuccessfulCount() {
        return successfulCount;
    }

    public int getErrorCount() {
        return errorCount;
    }

    public Optional<OperationMeta> getOperationMeta() {
        return Optional.ofNullable(operationMeta);
    }

    public ApiMassResult<R> withOperationMeta(OperationMeta operationMeta) {
        this.operationMeta = operationMeta;
        return this;
    }

    public static <T, V> ApiMassResult<T> successfulMassAction(List<T> items,
                                                               ValidationResult<List<V>, DefectType> massValidation, Set<Integer> canceledElementIndexes) {
        checkArgument(!massValidation.hasErrors(),
                "successful mass action result must not contain operational errors");
        checkArgument(items.size() == massValidation.getValue().size(),
                "Size of items should match size of value in validation");

        List<ApiResult<T>> elementsResults = createElementsResults(items, massValidation, canceledElementIndexes);

        List<DefectInfo<DefectType>> warnings = mapList(massValidation.getWarnings(),
                defectDefinition -> new DefectInfo<>(path(), massValidation.getValue(), defectDefinition));
        return new ApiMassResult<>(elementsResults, emptyList(), warnings, ApiResultState.SUCCESSFUL);
    }

    public static <T, V> ApiMassResult<T> successfulMassAction(List<T> items,
                                                               ValidationResult<List<V>, DefectType> massValidation) {
        return successfulMassAction(items, massValidation, emptySet());
    }

    public static <T, V> ApiMassResult<T> brokenMassAction(List<T> items,
                                                           ValidationResult<List<V>, DefectType> massValidation) {
        if (massValidation.hasErrors()) {
            return new ApiMassResult<>(null,
                    massValidation.flattenErrors(),
                    massValidation.flattenWarnings(),
                    ApiResultState.BROKEN);
        } else {
            List<ApiResult<T>> elementsResults = createElementsResults(items, massValidation, emptySet());
            List<DefectInfo<DefectType>> warnings = mapList(massValidation.getWarnings(),
                    defectDefinition -> new DefectInfo<>(path(), massValidation.getValue(), defectDefinition));
            return new ApiMassResult<>(elementsResults, emptyList(), warnings, ApiResultState.SUCCESSFUL);
        }
    }

    private static <T, V> List<ApiResult<T>> createElementsResults(List<T> resultItems,
                                                                   ValidationResult<List<V>, DefectType> massValidation,
                                                                   Set<Integer> canceledElementIndexes) {
        Map<PathNode, ValidationResult<?, DefectType>> validations = massValidation.getSubResults();
        List<ApiResult<T>> results = new ArrayList<>(resultItems.size());
        for (int i = 0; i < resultItems.size(); i++) {
            ValidationResult<?, DefectType> validation = validations.get(index(i));
            boolean hasErrors = validation != null && validation.hasAnyErrors();
            boolean canceled = canceledElementIndexes.contains(i);
            checkArgument(!(canceled && hasErrors), "canceled item %s must not contain errors", i);

            if (hasErrors) {
                results.add(ApiResult.broken(validation.flattenErrors(), validation.flattenWarnings()));
            } else {
                List<DefectInfo<DefectType>> warnings = validation != null ?
                        validation.flattenWarnings() : emptyList();

                if (canceled) {
                    results.add(ApiResult.canceled(warnings));
                } else {
                    T resultItem = resultItems.get(i);
                    results.add(ApiResult.successful(resultItem, warnings));
                }
            }
        }

        return results;
    }

    /**
     * Метод явно задаёт количества успешно обработанных объектов, и обработанных с ошибкой.
     * Эта информация используется для вычисления количества баллов, потраченных на выполнение операции.
     *
     * @param successfulCount количество успешно обработанных объектов
     * @param errorCount      количество объектов, при обработке которых возникли ошибки
     */
    public ApiMassResult<R> withCounts(int successfulCount, int errorCount) {
        this.successfulCount = successfulCount;
        this.errorCount = errorCount;
        return this;
    }

    @Override
    public int getSuccessfulObjectsCount() {
        return successfulCount;
    }

    @Override
    public int getUnsuccessfulObjectsCount() {
        return errorCount;
    }

    public ApiResult<R> get(int i) {
        return getResult().get(i);
    }
}
