package com.spring.common.util;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TypeUtils {
    
    public static TypeInfo extractParameterInfo(Parameter parameter, String requestMethod) {
        return TypeInfo.builder()
            .type(extractTypeName(parameter.getParameterizedType()))
            .paramName(parameter.getName())
            .sourceType(determineSourceType(parameter, requestMethod))
            .typeDetails(extractTypeDetails(parameter.getParameterizedType()))
            .build();
    }

    public static TypeInfo extractReturnTypeInfo(Type returnType) {
        return TypeInfo.builder()
            .type(extractTypeName(returnType))
            .typeDetails(extractTypeDetails(returnType))
            .build();
    }

    public static String extractRequestMethod(Method method) {
        return Arrays.stream(method.getAnnotations())
            .filter(annotation -> annotation.annotationType().getSimpleName().endsWith("Mapping"))
            .findFirst()
            .map(annotation -> {
                if (annotation instanceof RequestMapping) {
                    RequestMethod[] methods = ((RequestMapping) annotation).method();
                    return methods.length > 0 ? methods[0].name() : HttpMethod.GET.name();
                }
                return annotation.annotationType().getSimpleName()
                    .replace("Mapping", "")
                    .toUpperCase();
            })
            .orElse(HttpMethod.GET.name());
    }

    private static String extractTypeName(Type type) {
        if (type instanceof Class<?>) {
            return ((Class<?>) type).getSimpleName();
        }
        if (type instanceof ParameterizedType) {
            ParameterizedType paramType = (ParameterizedType) type;
            String rawType = ((Class<?>) paramType.getRawType()).getSimpleName();
            String typeArgs = Arrays.stream(paramType.getActualTypeArguments())
                .map(TypeUtils::extractTypeName)
                .collect(Collectors.joining(", "));
            return String.format("%s<%s>", rawType, typeArgs);
        }
        return type.getTypeName();
    }

    private static TypeDetails extractTypeDetails(Type type) {
        Class<?> baseClass = getBaseClass(type);
        Class<?> actualTypeClass = getActualType(type);
        
        // Collection 타입이면서 제네릭 타입이 복잡한 객체인 경우
        if (Collection.class.isAssignableFrom(baseClass) && isComplexType(actualTypeClass)) {
            return TypeDetails.builder()
                .isCollection(true)
                .fields(extractFields(actualTypeClass))
                .build();
        }
        // 일반 복잡한 객체인 경우
        else if (isComplexType(baseClass)) {
            return TypeDetails.builder()
                .isCollection(Collection.class.isAssignableFrom(baseClass))
                .fields(extractFields(baseClass))
                .build();
        }
        
        return null;
    }

    private static Class<?> getBaseClass(Type type) {
        if (type instanceof Class<?>) {
            return (Class<?>) type;
        }
        if (type instanceof ParameterizedType) {
            return (Class<?>) ((ParameterizedType) type).getRawType();
        }
        return null;
    }

    private static Class<?> getActualType(Type type) {
        if (type instanceof Class<?>) {
            return (Class<?>) type;
        }
        if (type instanceof ParameterizedType) {
            ParameterizedType paramType = (ParameterizedType) type;
            Type[] typeArgs = paramType.getActualTypeArguments();
            if (typeArgs.length > 0) {
                if (typeArgs[0] instanceof Class) {
                    return (Class<?>) typeArgs[0];
                } else if (typeArgs[0] instanceof ParameterizedType) {
                    // 중첩된 제네릭 타입의 경우 (ex: List<Map<String, Object>>)
                    return (Class<?>) ((ParameterizedType) typeArgs[0]).getRawType();
                }
            }
        }
        return null;
    }

    private static boolean isComplexType(Class<?> clazz) {
        return clazz != null &&
                !clazz.isPrimitive() &&
                !clazz.getName().startsWith("java.") &&
                !clazz.isEnum();
    }

    private static List<FieldInfo> extractFields(Class<?> clazz) {
        if (clazz == null) return Collections.emptyList();
        
        return Arrays.stream(clazz.getDeclaredFields())
            .filter(field -> !Modifier.isStatic(field.getModifiers()))
            .map(field -> new FieldInfo(
                extractTypeName(field.getGenericType()),
                field.getName()
            ))
            .collect(Collectors.toList());
    }

    private static SourceType determineSourceType(Parameter parameter, String requestMethod) {
        if (parameter.isAnnotationPresent(RequestBody.class)) return SourceType.BODY;
        if (parameter.isAnnotationPresent(RequestParam.class)) return SourceType.QUERY;
        if (parameter.isAnnotationPresent(PathVariable.class)) return SourceType.PATH;
        if (parameter.isAnnotationPresent(ModelAttribute.class) || isComplexType(parameter.getType())) {
            return "GET".equalsIgnoreCase(requestMethod) ? SourceType.QUERY : SourceType.FORM;
        }
        return SourceType.QUERY;
    }

    @Getter
    @Builder
    public static class TypeInfo {
        private final String type;
        private final String paramName;
        private final SourceType sourceType;
        private final TypeDetails typeDetails;
    }

    @Getter
    @Builder
    public static class TypeDetails {
        private final boolean isCollection;
        private final List<FieldInfo> fields;
    }

    @Getter
    @RequiredArgsConstructor
    public static class FieldInfo {
        private final String type;
        private final String name;
    }

    public enum SourceType {
        BODY, QUERY, PATH, FORM
    }
}
