package com.ruoyi.common.utils.translation;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.reflect.ReflectUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import org.springframework.jdbc.core.JdbcTemplate;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public final class Translator
{
    private static final String KEY_SQL_KEY = "$__SQL__";

    private final List<Object> handledObjects = new ArrayList<>();

    public Translator() {}

    public void translateList(List<?> list, Object...args)
    {
        if(CollectionUtil.isEmpty(list))
            return;
        if(startTranslate(list))
            list.forEach((x) -> translate(x, args));
    }

    @SuppressWarnings("unchecked")
    public void translate(Object obj, Object...args)
    {
        if(null == obj)
            return;
        if(obj instanceof BaseEntity)
        {
            Object o = DEF_PARM(args);
            boolean b = o instanceof Boolean ? (Boolean)o : false;
            translateEntity((BaseEntity)obj, b);
        }
        else if(obj instanceof Map)
        {
            Object o = DEF_PARM(args);
            Map<String, String> dictMap = o instanceof Map ? (Map<String, String>)args[0] : null;
            translateMap((Map<String, Object>)obj, dictMap);
        }
        else if(obj instanceof List)
        {
            translateList((List<?>)obj, args);
        }
        else
        {
            translateObject(obj);
        }
    }

    public <T extends BaseEntity> void translateEntityList(List<T> list, boolean keepRawField)
    {
        if(CollectionUtil.isEmpty(list))
            return;
        list.forEach((x) -> translateEntity(x, keepRawField));
    }

    public <T extends BaseEntity> void translateEntity(T object, boolean keepRawField)
    {
        if(null == object)
            return;
        translateEntity_r(object, object.getClass(), keepRawField);
    }

    public void translateMapList(List<Map<String, Object>> list, Map<String, String> dictMap)
    {
        if(CollectionUtil.isEmpty(list))
            return;
        list.forEach((x) -> translateMap(x, dictMap));
    }

    public void translateMap(Map<String, Object> map, Map<String, String> dictMap)
    {
        if(CollectionUtil.isEmpty(map) || CollectionUtil.isEmpty(dictMap))
            return;
        if(!startTranslate(map))
            return;
        dictMap.forEach((k, v) -> {
            if(!map.containsKey(k))
                return;
            Object o = map.get(k);
            if(null == o)
                return;
            Class<?> clazz = o.getClass();
            if(clazz.equals(String.class))
            {
                String str = (String)o;
                if(StringUtils.isEmpty(str))
                    return;
                map.put(k, DictUtils.getDictLabelElseOriginValue(v, str));
            }
            else if(canRecursionClassObjectField(clazz))
                translate(o, dictMap);
        });
    }

    // 判断是否翻译对象属性
    private boolean canRecursionClassObjectField(Class<?> clazz)
    {
/*        if(Object.class.equals(clazz)) // is Object class
            return false;
        if(!Object.class.isAssignableFrom(clazz)) // is internal class, e.g. int long boolean
            return false;
        if(clazz.getPackage().getName().startsWith("java.lang")) // class in java.lang.**, e.g. Long Integer
            return false;
        return true;*/
        return BaseEntity.class.isAssignableFrom(clazz) // BaseEntity
                || List.class.isAssignableFrom(clazz) // List
                || Map.class.isAssignableFrom(clazz) // Map
                ;
    }

    public <T> void translateObjectList(List<T> list)
    {
        if(CollectionUtil.isEmpty(list))
            return;
        list.forEach(this::translateObject);
    }

    public <T> void translateObject(T object)
    {
        if(null == object)
            return;
        translateObject_r(object, object.getClass());
    }

    private <T> void translateObject_r(T object, Class<?> clazz)
    {
        if(!startTranslate(object))
            return;
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field field : declaredFields)
        {
            boolean trans = hasTranslateFlag(field); // check translate flag
            if(!trans)
                continue;
            translateField(field, object);
        }
        Class<?> superclass = getSuperClass(clazz);
        if(null != superclass)
            translateObject_r(object, superclass);
    }

    private <T extends BaseEntity> void translateEntity_r(T object, Class<?> clazz, boolean keepRawField)
    {
        if(!startTranslate(object))
            return;

        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field field : declaredFields)
        {
            boolean trans = hasTranslateFlag(field); // check translate flag
            if(!trans)
                continue;
            translateField(field, object, keepRawField);
        }
        Class<?> superclass = getSuperClass(clazz);
        if(null != superclass)
            translateEntity_r(object, superclass, keepRawField);
    }

    // 解析readConverterExp
    private Map<String, String> parseReadConverterExp(String str)
    {
        Map<String, String> dict = new LinkedHashMap<>();
        if(StringUtils.isNotEmpty(str))
        {
            List<String> split = StrUtil.split(str, ',', true, true);
            split.forEach((x) -> {
                List<String> arr = StrUtil.split(x, '=', 2, true, true);
                if(CollectionUtil.isEmpty(arr))
                    return;
                dict.put(arr.get(0), arr.size() > 1 ? arr.get(1) : null);
            });
        }
        return dict;
    }

    // 读取数据库数据作为字典
    private Map<String, String> loadDBDict(String sql, String keyColumn, String nameColumn)
    {
        Map<String, String> dict = new LinkedHashMap<>();
        if(StringUtils.isNotEmpty(sql) && StringUtils.isNotEmpty(keyColumn) && StringUtils.isNotEmpty(nameColumn))
        {
            JdbcTemplate jdbcTemplate = SpringUtils.getBean(JdbcTemplate.class);
            try
            {
                List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
                if(CollectionUtil.isNotEmpty(list))
                {
                    for(Map<String, Object> map : list)
                    {
                        String key = toString_s(map.get(keyColumn));
                        if(StringUtils.isEmpty(key))
                            continue;
                        dict.put(key, toString_s(map.get(nameColumn)));
                    }
                }
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }
        return dict;
    }

    // 生成查询SQL
    private String genRelationSQL(String table, String keyColumn, String nameColumn)
    {
        if(StringUtils.isNotEmpty(table) && StringUtils.isNotEmpty(keyColumn) && StringUtils.isNotEmpty(nameColumn))
        {
            return String.format("SELECT `%s` FROM `%s` WHERE `%s` = ? LIMIT 1", nameColumn, table, keyColumn);
        }
        return null;
    }

    // 直接取SQL值
    private String readDBValue(String sql, String id)
    {
        if(StringUtils.isNotEmpty(sql) && StringUtils.isNotEmpty(id))
        {
            JdbcTemplate jdbcTemplate = SpringUtils.getBean(JdbcTemplate.class);
            try
            {
                List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, id);
                if(CollectionUtil.isNotEmpty(list))
                {
                    Map<String, Object> map = list.get(0);
                    return toString_s(map.get(map.keySet().iterator().next()));
                }
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }
        return null;
    }

    // 检查是否需要翻译
    private boolean hasTranslateFlag(Field field)
    {
        // 静态/不可重赋值 不翻译
        int modifiers = field.getModifiers();
        if(Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers))
            return false;

        return null != field.getAnnotation(Translate.class) || null != field.getAnnotation(Excel.class);
    }

    // 获取翻译来源
    // 返回: dictType对应的字典键值对和readConverterExp解析后的键值对的组合
    // 优先级: Translate > Excel | readConverterExp > dictType
    private Map<String, String> getTranslateDict(Field field)
    {
        Map<String, String> dict = new LinkedHashMap<>();
        String str;

        Translate translate = field.getAnnotation(Translate.class); // First check Translate annotation
        if(null != translate)
        {
            // 1. SQL 直查
            str = translate.sql();
            if(StringUtils.isNotEmpty(str))
            {
                dict.put(KEY_SQL_KEY, str);
            }
            // 2. table + tableKeyColumn + tableValueColumn 直查
            str = translate.table();
            if(StringUtils.isNotEmpty(str))
            {
                str = genRelationSQL(str, translate.tableKeyColumn(), translate.tableValueColumn());
                dict.put(KEY_SQL_KEY, str);
            }

            // 3. dict缓存
            str = translate.dictType();
            if(StringUtils.isNotEmpty(str))
            {
                dict.putAll(DictUtils.dictCacheValueLabelMap(str));
            }
            // 4. readConverterExp配置
            str = translate.readConverterExp();
            if(StringUtils.isNotEmpty(str))
            {
                dict.putAll(parseReadConverterExp(str));
            }
            // 5. 数据库任意表数据
            str = translate.dictSql();
            if(StringUtils.isNotEmpty(str))
            {
                dict.putAll(loadDBDict(str, translate.tableKeyColumn(), translate.tableValueColumn()));
            }
        }
        if(StringUtils.isNotEmpty(dict))
            return dict;

        Excel excel = field.getAnnotation(Excel.class); // And then check Excel annotation
        if(null != excel)
        {
            str = excel.dictType();
            if(StringUtils.isNotEmpty(str))
            {
                dict.putAll(DictUtils.dictCacheValueLabelMap(str));
            }
            str = excel.readConverterExp();
            if(StringUtils.isNotEmpty(str))
            {
                dict.putAll(parseReadConverterExp(str));
            }
        }
        return dict;
    }

    private boolean translateField(Field field, Object object)
    {
        if(field.getType().equals(String.class))
            return translateField_String(field, object);
        else
            return translateField_Object(field, object);
    }

    private boolean translateField_Object(Field field, Object object)
    {
        if(field.getType().equals(String.class))
            return false;

        if(!canRecursionClassObjectField(field.getType()))
            return false;

        Object val = null;
        if(!field.isAccessible())
            field.setAccessible(true);
        try
        {
            val = field.get(object);
        }
        catch(IllegalAccessException e)
        {
            e.printStackTrace();
        }
        if(null != val)
        {
            translate(val);
            return true;
        }
        return false;
    }

    private boolean translateField_String(Field field, Object object)
    {
        boolean trans = false;

        if(!field.getType().equals(String.class))
            return false;

        Map<String, String> dict = getTranslateDict(field);
        if(CollectionUtil.isEmpty(dict))
            return false;

        if(!field.isAccessible())
            field.setAccessible(true);
        try
        {
            Object val = getField(object, field);
            if(null != val)
            {
                String str = (String)val;
                if(StringUtils.isNotEmpty(str))
                {
                    if(dict.containsKey(KEY_SQL_KEY))
                    {
                        String dbVal = readDBValue(dict.get(KEY_SQL_KEY), str);
                        if(StringUtils.isNotEmpty(dbVal))
                        {
                            field.set(object, dbVal);
                            trans = true;
                        }
                    }
                    if(!trans && dict.containsKey(str))
                    {
                        field.set(object, dict.get(str));
                        trans = true;
                    }
                }
            }
        }
        catch(IllegalAccessException e)
        {
            e.printStackTrace();
            return false;
        }
        return trans;
    }

    private <T extends BaseEntity> boolean translateField(Field field, T entity, boolean keepRawField)
    {
        if(field.getType().equals(String.class))
            return translateField_String(field, entity, keepRawField);
        else
            return translateField_Object(field, entity, keepRawField);
    }

    private <T extends BaseEntity> boolean translateField_Object(Field field, T entity, boolean keepRawField)
    {
        if(field.getType().equals(String.class))
            return false;

        if(!canRecursionClassObjectField(field.getType()))
            return false;

        Object val = null;
        try
        {
            val = getField(entity, field);
        }
        catch(IllegalAccessException e)
        {
            e.printStackTrace();
        }
        if(null != val)
        {
            translate(val, keepRawField);
            return true;
        }
        return false;
    }

    private <T extends BaseEntity> boolean translateField_String(Field field, T entity, boolean keepRawField)
    {
        boolean trans = false;

        if(!field.getType().equals(String.class))
            return false;

        Map<String, String> dict = getTranslateDict(field);
        if(CollectionUtil.isEmpty(dict))
            return false;

        try
        {
            Object val = getField(entity, field);
            if(keepRawField)
                entity.getParams().put(field.getName(), val); // backup raw value to params
            if(null != val)
            {
                String str = (String)val;
                if(StringUtils.isNotEmpty(str))
                {
                    if(dict.containsKey(KEY_SQL_KEY))
                    {
                        String dbVal = readDBValue(dict.get(KEY_SQL_KEY), str);
                        if(StringUtils.isNotEmpty(dbVal))
                        {
                            field.set(entity, dbVal);
                            trans = true;
                        }
                    }
                    if(!trans && dict.containsKey(str))
                    {
                        field.set(entity, dict.get(str));
                        trans = true;
                    }
                }
            }
        }
        catch(IllegalAccessException e)
        {
            e.printStackTrace();
            return false;
        }
        return trans;
    }

    private Object getField(Object obj, Field field) throws IllegalAccessException
    {
        if(!field.isAccessible())
            field.setAccessible(true);
        Translate annotation = field.getAnnotation(Translate.class);
        if(null != annotation)
        {
            String refProp = annotation.referenceProperty();
            if(StringUtils.isNotEmpty(refProp))
            {
                Field refField = ReflectUtils.getAccessibleField(obj, refProp);
                if(null != refField)
                    return toString_s(refField.get(obj)); // 转换为String
            }
        }
        return field.get(obj);
    }

    private Class<?> getSuperClass(Class<?> clazz)
    {
        if(null == clazz)
            return null;
        Class<?> superclass = clazz.getSuperclass(); // translate parent class
        if(null != superclass && !superclass.equals(Object.class)) // check is top or is Object
            return superclass;
        return null;
    }

    // 允许翻译则返回true
    private boolean startTranslate(Object o)
    {
        if(null == o)
            return false;
        if(o instanceof String) // String类型总是重新翻译
            return true;
        if(contains_ptr(handledObjects, o)) // 其他类型防止循环递归
        {
            return false;
        }
        handledObjects.add(o);
        return true;
    }

    public void Reset()
    {
        handledObjects.clear();
    }


    public static <T> boolean contains_ptr(Collection<T> list, T target)
    {
        if(CollectionUtil.isEmpty(list))
            return false;
        return list.stream().anyMatch((x) -> x == target);
    }

    public static String toString_s(Object obj)
    {
        if(null == obj)
            return "";
        if(obj instanceof String)
            return (String)obj;
        else
            return obj.toString();
    }

    public static <T> T DEF_PARM(T...args)
    {
        return null != args && args.length > 0 ? args[0] : null;
    }
}
