/*
 * Hibernate Validator, declare and validate application constraints
 *
 * License: Apache License, Version 2.0
 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
 */
package org.hibernate.validator.test.internal.engine.valuehandling;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.executable.ExecutableValidator;

import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import org.hibernate.validator.testutil.TestForIssue;
import org.hibernate.validator.valuehandling.UnwrapValidatedValue;

import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertCorrectConstraintTypes;
import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertCorrectPropertyPaths;
import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertNumberOfViolations;
import static org.hibernate.validator.testutils.ValidatorUtil.getValidator;

/**
 * Tests for {@link org.hibernate.validator.internal.engine.valuehandling.OptionalValueUnwrapper}.
 *
 * @author Khalid Alqinyah
 * @author Hardy Ferentschik
 */
public class OptionalValueUnwrapperTest {

	private Validator validator;

	@BeforeClass
	public void setup() {
		validator = getValidator();
	}

	@Test
	public void testOptionalUnwrappedValueViolations() {
		Set<ConstraintViolation<Foo>> constraintViolations = validator.validate( new Foo() );
		assertNumberOfViolations( constraintViolations, 2 );
		assertCorrectPropertyPaths(
				constraintViolations,
				"integerOptional",
				"optionalLong"
		);
		assertCorrectConstraintTypes( constraintViolations, Min.class, Max.class );
	}

	@Test
	public void testOptionalCascadedValidation() {
		Set<ConstraintViolation<Snafu>> constraintViolations = validator.validate( new Snafu() );
		assertNumberOfViolations( constraintViolations, 1 );
		assertCorrectPropertyPaths(
				constraintViolations,
				"barOptional.number"
		);
		assertCorrectConstraintTypes( constraintViolations, Min.class );
	}

	@Test
	public void testOptionalUnwrappedExecutableReturnValue() throws Exception {
		ExecutableValidator executableValidator = validator.forExecutables();
		Method method = Foo.class.getMethod( "getOptionalLong" );
		Set<ConstraintViolation<Foo>> constraintViolations = executableValidator.validateReturnValue(
				new Foo(),
				method,
				Optional.of( 9L )
		);
		assertNumberOfViolations( constraintViolations, 1 );
		assertCorrectPropertyPaths( constraintViolations, "getOptionalLong.<return value>" );
		assertCorrectConstraintTypes( constraintViolations, Max.class );
	}

	@Test
	public void testOptionalUnwrappedExecutableParameter() throws Exception {
		ExecutableValidator executableValidator = validator.forExecutables();
		Method method = Baz.class.getMethod( "setOptionalLong", Optional.class );
		Object[] values = { Optional.of( 2L ) };
		Set<ConstraintViolation<Baz>> constraintViolations = executableValidator.validateParameters(
				new Baz(),
				method,
				values
		);
		assertNumberOfViolations( constraintViolations, 1 );
		assertCorrectPropertyPaths( constraintViolations, "setOptionalLong.arg0" );
		assertCorrectConstraintTypes( constraintViolations, Min.class );
	}

	@Test
	public void testOptionalUnwrappedCascadableExecutableReturnValue() throws Exception {
		ExecutableValidator executableValidator = validator.forExecutables();
		Method method = Qux.class.getMethod( "getBar" );
		Set<ConstraintViolation<Qux>> constraintViolations = executableValidator.validateReturnValue(
				new Qux(),
				method,
				Optional.of( new Bar() )
		);
		assertNumberOfViolations( constraintViolations, 1 );
		assertCorrectPropertyPaths( constraintViolations, "getBar.<return value>.number" );
		assertCorrectConstraintTypes( constraintViolations, Min.class );
	}

	@Test
	public void testOptionalUnwrappedCascadableExecutableParameter() throws Exception {
		ExecutableValidator executableValidator = validator.forExecutables();
		Method method = Fubar.class.getMethod( "setBar", Optional.class );
		Object[] values = { Optional.of( new Bar() ) };
		Set<ConstraintViolation<Fubar>> constraintViolations = executableValidator.validateParameters(
				new Fubar(),
				method,
				values
		);
		assertNumberOfViolations( constraintViolations, 1 );
		assertCorrectPropertyPaths( constraintViolations, "setBar.arg0.number" );
		assertCorrectConstraintTypes( constraintViolations, Min.class );
	}

	@Test
	@TestForIssue(jiraKey = "HV-895")
	public void cascaded_validation_applies_for_elements_of_list_wrapped_in_optional() {
		Set<ConstraintViolation<Quux>> constraintViolations = validator.validate( new Quux() );
		assertNumberOfViolations( constraintViolations, 2 );
		assertCorrectPropertyPaths(
				constraintViolations,
				"bar[0].number",
				"bar[1].number"
		);
		assertCorrectConstraintTypes( constraintViolations, Min.class, Min.class );
	}

	@SuppressWarnings("unused")
	private class Foo {
		@Min(value = 5)
		Optional<Integer> integerOptional = Optional.of( 3 );

		@Max(value = 5)
		public Optional<Long> getOptionalLong() {
			return Optional.of( 7L );
		}
	}

	@SuppressWarnings("unused")
	private class Snafu {
		@Valid
		Optional<Bar> barOptional = Optional.of( new Bar() );
	}

	@SuppressWarnings("unused")
	private class Fubar {
		public void setBar(@UnwrapValidatedValue @Valid Optional<Bar> optionalBarPara) {
		}
	}

	@SuppressWarnings("unused")
	private class Qux {
		@Valid
		public Optional<Bar> getBar() {
			return Optional.empty();
		}
	}

	@SuppressWarnings("unused")
	private class Baz {
		public void setOptionalLong(@UnwrapValidatedValue @Min(5) Optional<Long> optionalLongPara) {
		}
	}

	@SuppressWarnings("unused")
	private class Bar {
		@Min(value = 5)
		int number = 3;
	}

	@SuppressWarnings("unused")
	private class Quux {
		@Valid
		public Optional<List<Bar>> getBar() {
			return Optional.of( Arrays.asList( new Bar(), new Bar() ) );
		}
	}
}
