package com.njzscloud.common.http.resolver;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.njzscloud.common.core.tuple.Tuple2;
import com.njzscloud.common.core.tuple.Tuple3;
import com.njzscloud.common.http.constant.HttpMethod;
import lombok.RequiredArgsConstructor;

import java.io.File;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.*;

@RequiredArgsConstructor
public abstract class ParamResolver<T extends Annotation, R> {
    protected final Class<T> annoType;

    abstract public R resolve(HttpMethod httpMethod, String urlTpl);

    public final void resolve(Parameter parameter, Object obj) {
        if (obj == null) return;

        Class<?> parameterClazz = obj.getClass();
        String parameterName;
        T parameterAnno = null;
        if (parameter == null) {
            parameterName = StrUtil.lowerFirst(obj.getClass().getName());
        } else {
            parameterName = parameter.getName();
            parameterAnno = parameter.getAnnotation(this.annoType);
        }

        if (parameterAnno == null) {
            parameterAnno = parameterClazz.getAnnotation(this.annoType);
        }

        int state = 0;

        if (parameterAnno != null) {
            state = this.resolveParameter(parameterAnno, parameterName, parameterClazz, obj) ?
                    1 :
                    2;
        }

        if (state == 1) return;

        List<Method> getterMethods = ReflectUtil.getPublicMethods(parameterClazz,
                it -> (it.getName().startsWith("get") || it.getName().startsWith("is")) &&
                        !Modifier.isStatic(it.getModifiers()) &&
                        !it.getName().equals("getClass")
        );

        for (Method getterMethod : getterMethods) {
            Object fieldValue = ReflectUtil.invoke(obj, getterMethod);
            if (fieldValue == null) continue;
            String getterMethodName = getterMethod.getName();
            Class<?> returnType = getterMethod.getReturnType();
            String fieldName = StrUtil.lowerFirst(getterMethodName.startsWith("get") ?
                    getterMethodName.substring(3) :
                    getterMethodName.substring(2));

            T fieldAnno = getterMethod.getAnnotation(this.annoType);
            if (fieldAnno == null) {
                Field field = ReflectUtil.getField(parameterClazz, fieldName);
                fieldAnno = field.getAnnotation(this.annoType);
            }

            if (fieldAnno == null && state == 0) continue;

            this.resolveField(state == 2, fieldAnno, fieldName, returnType, fieldValue);
        }
    }

    public final void resolve(Object obj) {
        resolve(null, obj);
    }

    protected abstract boolean resolveParameter(T anno, String paramName, Class<?> paramClazz, Object paramValue);

    protected abstract boolean resolveField(boolean hasParameterAnno, T anno, String paramName, Class<?> paramClazz, Object paramValue);


    public final boolean isNum(Class<?> clazz) {
        return clazz == byte.class || clazz == short.class ||
                clazz == int.class || clazz == long.class ||
                clazz == float.class || clazz == double.class
                || Number.class.isAssignableFrom(clazz);
    }

    protected final boolean isStr(Class<?> clazz) {
        return clazz == String.class || clazz == char.class || clazz == Character.class;
    }

    protected final boolean isDt(Class<?> clazz) {
        return clazz == LocalDate.class ||
                clazz == LocalTime.class ||
                clazz == LocalDateTime.class ||
                Date.class.isAssignableFrom(clazz);
    }

    protected final boolean isArr(Class<?> clazz) {
        return Collection.class.isAssignableFrom(clazz) || clazz.isArray();
    }

    protected final Collection<?> toArr(Object obj) {
        if (obj == null) return null;
        Class<?> clazz = obj.getClass();
        if (clazz.isArray()) {
            return Arrays.asList((Object[]) obj);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            return (Collection<?>) obj;
        } else {
            throw new RuntimeException("不是数组");
        }
    }

    protected final boolean isKv(Class<?> clazz) {
        return Map.class.isAssignableFrom(clazz);
    }

    protected final boolean isIn(Class<?> clazz) {
        return InputStream.class.isAssignableFrom(clazz);
    }

    protected final boolean isBytes(Class<?> clazz) {
        return clazz == byte[].class || clazz == Byte[].class;
    }

    protected final Tuple2<String, byte[]> toFileInfo(Object obj) {
        if (obj == null) return null;
        Class<?> clazz = obj.getClass();
        if (isIn(clazz)) {
            byte[] bytes = IoUtil.readBytes((InputStream) obj);
            return Tuple3.create(null, bytes);
        } else if (isFile(clazz)) {
            byte[] bytes = FileUtil.readBytes((File) obj);
            String name = ((File) obj).getName();
            return Tuple3.create(name, bytes);
        } else {
            throw new RuntimeException("不是文件");
        }
    }

    protected final boolean isFile(Class<?> clazz) {
        return File.class.isAssignableFrom(clazz);
    }


    protected final String formatDt(String format, Object value) {
        if (value == null) return null;
        Class<?> clazz = value.getClass();
        if (Date.class.isAssignableFrom(clazz)) {
            if (StrUtil.isBlank(format)) format = DatePattern.NORM_DATETIME_PATTERN;
            return DateUtil.format(((Date) value), format);
        } else if (LocalDateTime.class.isAssignableFrom(clazz)) {
            if (StrUtil.isBlank(format)) format = DatePattern.NORM_DATETIME_PATTERN;
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
            return dateTimeFormatter.format((TemporalAccessor) value);
        } else if (LocalDate.class.isAssignableFrom(clazz)) {
            if (StrUtil.isBlank(format)) format = DatePattern.NORM_DATE_PATTERN;
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
            return dateTimeFormatter.format((TemporalAccessor) value);
        } else if (LocalTime.class.isAssignableFrom(clazz)) {
            if (StrUtil.isBlank(format)) format = DatePattern.NORM_TIME_PATTERN;
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
            return dateTimeFormatter.format((TemporalAccessor) value);
        } else {
            throw new RuntimeException("不是时间");
        }
    }

    protected final String formatDt(Object value) {
        return formatDt(null, value);
    }

    protected final String formatNum(String format, RoundingMode roundingMode, Object value) {
        if (value == null) return null;
        if (StrUtil.isBlank(format)) return formatNum(value);

        Class<?> clazz = value.getClass();
        if (isNum(clazz)) {
            return NumberUtil.decimalFormat(format, value, roundingMode);
        } else {
            throw new RuntimeException("不是数字");
        }
    }

    protected final String formatNum(String format, Object value) {
        return formatNum(format, null, value);
    }

    protected final String formatNum(Object value) {
        if (value == null) return null;

        Class<?> clazz = value.getClass();
        if (isNum(clazz)) {
            if (clazz == BigDecimal.class) {
                return ((BigDecimal) value).toPlainString();
            } else {
                return value.toString();
            }
        } else {
            throw new RuntimeException("不是数字");
        }
    }


    protected void resolveKv(Object paramValue) {
        ((Map<?, ?>) paramValue).forEach((k, v) -> {
            if (v == null) return;
            resolveFormable(null, k.toString(), v.getClass(), v);
        });
    }

    protected void resolveArr(String paramName, Object paramValue) {
        toArr(paramValue)
                .stream()
                .filter(Objects::nonNull)
                .forEach(it -> resolveFormable(null, paramName, it.getClass(), it));
    }

    protected void resolveFormable(T anno, String paramName, Class<?> paramClazz, Object paramValue) {
    }

}
