/*
 * Copyright 2002-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.aot.hint.support;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Consumer;

import org.junit.jupiter.api.Test;

import org.springframework.aot.hint.JdkProxyHint;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.SynthesizedAnnotation;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Tests for {@link RuntimeHintsUtils}.
 *
 * @author Stephane Nicoll
 */
class RuntimeHintsUtilsTests {

	private final RuntimeHints hints = new RuntimeHints();

	@Test
	void registerAnnotationType() {
		RuntimeHintsUtils.registerAnnotation(this.hints, SampleInvoker.class);
		assertThat(this.hints.reflection().typeHints()).singleElement()
				.satisfies(annotationHint(SampleInvoker.class));
		assertThat(this.hints.proxies().jdkProxies()).isEmpty();
	}

	@Test
	void registerAnnotationTypeWithLocalUseOfAliasForRegistersProxy() {
		RuntimeHintsUtils.registerAnnotation(this.hints, LocalMapping.class);
		assertThat(this.hints.reflection().typeHints()).singleElement()
				.satisfies(annotationHint(LocalMapping.class));
		assertThat(this.hints.proxies().jdkProxies()).singleElement()
				.satisfies(annotationProxy(LocalMapping.class));
	}

	@Test
	void registerAnnotationTypeProxyRegistersJdkProxy() {
		RuntimeHintsUtils.registerAnnotation(this.hints, RetryInvoker.class);
		assertThat(this.hints.reflection().typeHints())
				.anySatisfy(annotationHint(RetryInvoker.class))
				.anySatisfy(annotationHint(SampleInvoker.class));
		assertThat(this.hints.proxies().jdkProxies()).singleElement()
				.satisfies(annotationProxy(RetryInvoker.class));
	}

	@Test
	void registerAnnotationTypeWhereUsedAsAMetaAnnotationRegistersHierarchy() {
		RuntimeHintsUtils.registerAnnotation(this.hints, RetryWithEnabledFlagInvoker.class);
		ReflectionHints reflection = this.hints.reflection();
		assertThat(reflection.typeHints())
				.anySatisfy(annotationHint(RetryWithEnabledFlagInvoker.class))
				.anySatisfy(annotationHint(RetryInvoker.class))
				.anySatisfy(annotationHint(SampleInvoker.class))
				.hasSize(3);
		assertThat(this.hints.proxies().jdkProxies()).singleElement()
				.satisfies(annotationProxy(RetryWithEnabledFlagInvoker.class));
	}

	private Consumer<TypeHint> annotationHint(Class<?> type) {
		return typeHint -> {
			assertThat(typeHint.getType()).isEqualTo(TypeReference.of(type));
			assertThat(typeHint.constructors()).isEmpty();
			assertThat(typeHint.fields()).isEmpty();
			assertThat(typeHint.methods()).isEmpty();
			assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS);
		};
	}

	private Consumer<JdkProxyHint> annotationProxy(Class<?> type) {
		return jdkProxyHint -> assertThat(jdkProxyHint.getProxiedInterfaces())
				.containsExactly(TypeReference.of(type),
						TypeReference.of(SynthesizedAnnotation.class));
	}


	@SampleInvoker
	static class SampleInvokerClass {
	}

	@RetryInvoker
	static class RetryInvokerClass {
	}

	@RetryWithEnabledFlagInvoker
	static class RetryWithEnabledFlagInvokerClass {
	}

	@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	@interface LocalMapping {

		@AliasFor("retries")
		int value() default 0;

		@AliasFor("value")
		int retries() default 0;

	}


	@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	@interface SampleInvoker {

		int retries() default 0;

	}

	@Target({ ElementType.TYPE })
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	@SampleInvoker
	@interface RetryInvoker {

		@AliasFor(attribute = "retries", annotation = SampleInvoker.class)
		int value() default 1;

	}

	@Target({ ElementType.TYPE })
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	@RetryInvoker
	@interface RetryWithEnabledFlagInvoker {

		@AliasFor(attribute = "value", annotation = RetryInvoker.class)
		int value() default 5;

		boolean enabled() default true;

	}

}

@@ -667,89 +667,8 @@
 t;%0A%0A
-import java.lang.annotation.Documented;%0Aimport java.lang.annotation.ElementType;%0A
 impo
@@ -751,44 +751,8 @@
 cy;%0A
-import java.lang.annotation.Target;%0A
 impo
@@ -3796,259 +3796,8 @@
 %7D%0A%0A%0A
-%09@SampleInvoker%0A%09static class SampleInvokerClass %7B%0A%09%7D%0A%0A%09@RetryInvoker%0A%09static class RetryInvokerClass %7B%0A%09%7D%0A%0A%09@RetryWithEnabledFlagInvoker%0A%09static class RetryWithEnabledFlagInvokerClass %7B%0A%09%7D%0A%0A%09@Target(%7B ElementType.TYPE, ElementType.ANNOTATION_TYPE %7D)%0A
 %09@Re
@@ -3825,37 +3825,24 @@
 cy.RUNTIME)%0A
-%09@Documented%0A
 %09@interface 
@@ -3964,68 +3964,8 @@
 %7D%0A%0A%0A
-%09@Target(%7B ElementType.TYPE, ElementType.ANNOTATION_TYPE %7D)%0A
 %09@Re
@@ -3997,29 +3997,16 @@
 UNTIME)%0A
-%09@Documented%0A
 %09@interf
@@ -4062,39 +4062,8 @@
 %09%7D%0A%0A
-%09@Target(%7B ElementType.TYPE %7D)%0A
 %09@Re
@@ -4095,29 +4095,16 @@
 UNTIME)%0A
-%09@Documented%0A
 %09@Sample
@@ -4242,39 +4242,8 @@
 %09%7D%0A%0A
-%09@Target(%7B ElementType.TYPE %7D)%0A
 %09@Re
@@ -4279,21 +4279,8 @@
 ME)%0A
-%09@Documented%0A
 %09@Re

Simplify RuntimeHintsUtilsTests