package ru.yandex.direct.api.v5.ws.validation;

import java.util.Comparator;
import java.util.Optional;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import ru.yandex.direct.core.TranslatableException;

import static ru.yandex.direct.api.v5.ws.validation.ValidationUtil.simpleViolationPathConverter;

/**
 * Проверяет объект запроса API по аннотациям JSR 303, сгенерированным по WSDL.
 * <p>
 * При ошибках выкидывает исключение типа {@link TranslatableException}.
 */
@Component
public class ApiObjectValidator {
    private static final Logger logger = LoggerFactory.getLogger(ApiObjectValidator.class);
    private ValidatorFactory validatorFactory;

    public ApiObjectValidator() {
        validatorFactory = Validation.buildDefaultValidatorFactory();
    }

    public void validate(Object obj) {
        Validator validator = validatorFactory.getValidator();
        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(obj);

        // Если ошибки есть, сообщаем только об одной
        // Чтобы результат был стабильным относительно входных данных, упорядочиваем violation'ы
        Optional<ConstraintViolation<Object>> violationOptional =
                constraintViolations.stream().min(getComparingStrategy());

        if (!violationOptional.isPresent()) {
            return;
        }
        logger.debug("Object {} has {} constraint violations", obj, constraintViolations.size());

        ConstraintViolation<Object> violation = violationOptional.get();
        Object constraintType = violation.getConstraintDescriptor().getAnnotation();
        String path = simpleViolationPathConverter(violation.getPropertyPath());

        if (constraintType instanceof NotNull) {
            throw NullValueApiException.fromConstraintViolation(violation);
        } else if (constraintType instanceof Size) {
            Size sizeConstraint = (Size) constraintType;
            throw new InvalidSizeApiException(path, sizeConstraint.min(), sizeConstraint.max());
        } else {
            logger.warn("Unsupported constraint violation {}", violation);
            throw new InvalidValueApiException(path);
        }
    }

    private static Comparator<ConstraintViolation<Object>> getComparingStrategy() {
        return Comparator.comparing(cv -> simpleViolationPathConverter(cv.getPropertyPath()));
    }

}
