package com.xebia.jacksonlombok;

import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Collections;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.util.BeanUtil;

public class JacksonLombokAnnotationIntrospector extends JacksonAnnotationIntrospector {
    @Override
    public boolean hasCreatorAnnotation(Annotated annotated) {
        if (super.hasCreatorAnnotation(annotated)) {
            return true;
        } else if (!(annotated instanceof AnnotatedConstructor)) {
            return false;
        } else {
            AnnotatedConstructor annotatedConstructor = (AnnotatedConstructor) annotated;

            ConstructorProperties properties = getConstructorPropertiesAnnotation(annotatedConstructor);

            if (properties == null) {
                return false;
            } else {
                addJacksonAnnotationsToContructorParameters(annotatedConstructor);
                return true;
            }
        }
    }

    private void addJacksonAnnotationsToContructorParameters(AnnotatedConstructor annotatedConstructor) {
        ConstructorProperties properties = getConstructorPropertiesAnnotation(annotatedConstructor);
        for (int i = 0; i < annotatedConstructor.getParameterCount(); i++) {
            String name = properties.value()[i];
            AnnotatedParameter parameter = annotatedConstructor.getParameter(i);
            Field field = null;
            try {
                field = annotatedConstructor.getDeclaringClass().getDeclaredField(name);
            } catch (NoSuchFieldException ignored) {
            }
            addJacksonAnnotationsToConstructorParameter(field, parameter, name);
        }
    }

    private void addJacksonAnnotationsToConstructorParameter(Field field, AnnotatedParameter parameter, String name) {
        if (field != null) {
            for (Annotation a : field.getAnnotations()) {
                if (a.annotationType().getName().startsWith("com.fasterxml")) {
                    if (a.annotationType() != JsonProperty.class) {
                        parameter.addOrOverride(a);
                    } else {
                        JsonProperty jp = (JsonProperty) a;
                        if (!jp.value().equals("")) {
                            name = jp.value();
                        }
                    }
                }
            }
        }

        JsonProperty jsonProperty =
                ProxyAnnotation.of(JsonProperty.class, Collections.singletonMap("value", name));
        parameter.addOrOverride(jsonProperty);
    }

    private ConstructorProperties getConstructorPropertiesAnnotation(AnnotatedConstructor annotatedConstructor) {
        Constructor<?> constructor = annotatedConstructor.getAnnotated();
        return constructor.getAnnotation(ConstructorProperties.class);
    }


    @Override
    public String findImplicitPropertyName(AnnotatedMember member) {
        JsonProperty property = member.getAnnotation(JsonProperty.class);
        if (property == null) {
            if (member instanceof AnnotatedMethod) {
                AnnotatedMethod method = (AnnotatedMethod) member;
                String fieldName = BeanUtil.okNameForGetter(method);
                return getJacksonPropertyName(member.getDeclaringClass(), fieldName);
            }
        } else if (!property.value().equals("")) {
            return property.value();
        }

        return null;
    }

    private String getJacksonPropertyName(Class<?> declaringClass, String fieldName) {
        if (fieldName != null) {
            try {
                Field field = declaringClass.getDeclaredField(fieldName);
                if (field != null) {
                    JsonProperty fieldProperty = field.getAnnotation(JsonProperty.class);
                    if (fieldProperty != null && !fieldProperty.value().equals("")) {
                        return fieldProperty.value();
                    }
                }
            } catch (NoSuchFieldException ignored) {
            }
        }
        return null;
    }
}