package com.arms.egovframework.javaservice.esframework.util;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.StreamSupport;

import static java.util.stream.Collectors.toList;

public class ReflectionUtil {

    private ReflectionUtil(){}

    public static <S> List<Object> fieldValues(Iterable<S> entities, Class<? extends Annotation> annotation){
        return StreamSupport.stream(entities.spliterator(), false)
            .toList()
            .stream()
            .filter(Objects::nonNull)
            .map(a -> {
                try {
                    String name = fieldInfo(a.getClass(), annotation).getName();
                    return getValue(a,name);
                } catch (Exception e) {
                    throw new IllegalArgumentException(e);
                }
            }).collect(toList());
    }

    public static <S> Object fieldValue(S entity, Class<? extends Annotation> annotation)  {
        String name = fieldInfo(entity.getClass(), annotation).getName();
        try {
            return getValue(entity,name);
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    public static <S> Field fieldInfo(Iterable<S> entities, Class<? extends Annotation> annotation){
        return StreamSupport.stream(entities.spliterator(), false)
            .toList()
            .stream()
            .filter(Objects::nonNull)
            .findAny()
            .map(a -> fieldInfo(a.getClass(),annotation))
            .orElseThrow(() -> new RuntimeException("fieldInfos::해당 어노테이션이 지정되어 있지 않습니다."));
    }

    public static Method methodInfo(Class<?> entityClass, Class<? extends Annotation> annotation){
        return Arrays.stream(entityClass.getDeclaredMethods())
            .filter(m -> m.isAnnotationPresent(annotation))
            .findAny().orElseThrow(() -> new RuntimeException("methodInfo::해당 어노테이션이 지정되어 있지 않습니다."));
    }

    public static<T> void setValue(T instance, String methodName, Object value)  {
        String setMethodName = "set"+capitalize(methodName);
        Method method = Arrays.stream(instance.getClass().getDeclaredMethods())
            .filter(m -> setMethodName.equals(m.getName()))
            .findAny().orElseThrow(() -> new RuntimeException("setValue::해당 메소드가 지정되어 있지 않습니다."));
        try{
            method.invoke(instance, value);
        }catch (Exception e){
            throw new IllegalArgumentException("setValue::"+e.getMessage());
        }
    }

    public static<T> Object getValue(T instance, String methodName)  {
        String getMethodName = "get"+capitalize(methodName);
        Method method = Arrays.stream(instance.getClass().getDeclaredMethods())
                .filter(m -> getMethodName.equals(m.getName()))
                .findAny().orElseThrow(() -> new RuntimeException("getValue::" + getMethodName + "해당 메소드가 지정되어 있지 않습니다."));
        try{
            return method.invoke(instance);
        }catch (Exception e){
            throw new IllegalArgumentException("getValue::"+e.getMessage());
        }
    }


    public static Boolean isExistFieldWithAnnotation(Class<?> entityClass, Class<? extends Annotation> annotation){
        return Arrays.stream(entityClass.getDeclaredFields())
                .anyMatch(m -> m.isAnnotationPresent(annotation));
    }

    public static Field fieldInfo(Class<?> entityClass, Class<? extends Annotation> annotation){
        return Arrays.stream(entityClass.getDeclaredFields())
                .filter(m -> m.isAnnotationPresent(annotation))
                .findAny().orElseThrow(() -> new RuntimeException("fieldInfo::해당 어노테이션이 지정되어 있지 않습니다."));
    }

    public static Field[] fieldsInfos(Class<?> entityClass, Class<? extends Annotation> annotation){
        return Arrays.stream(entityClass.getDeclaredFields())
                .filter(m -> m.isAnnotationPresent(annotation))
                .toArray(Field[]::new);
    }

    private static String capitalize(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return Character.toUpperCase(str.charAt(0)) + str.substring(1);
    }

    public static Set<String> findAnnotatedClasses(Class<? extends Annotation> annotationType
            , String... packagesToBeScanned){
        var provider = new ClassPathScanningCandidateComponentProvider(false);
        provider.addIncludeFilter(new
                AnnotationTypeFilter(annotationType));

        Set<String> ret = new HashSet<>();

        for (var pkg : packagesToBeScanned) {
            Set<BeanDefinition> beanDefs = provider.findCandidateComponents(pkg);
            beanDefs.stream()
                    .map(BeanDefinition::getBeanClassName)
                    .forEach(ret::add);
        }

        return ret;
    }

}
