package com.persagy.proxy.adm.service.impl;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import cn.hutool.core.bean.BeanUtil;
import org.springframework.beans.factory.annotation.Autowired;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.persagy.dmp.basic.model.QueryCriteria;
import com.persagy.dmp.common.constant.ValidEnum;
import com.persagy.dmp.common.helper.SpringHelper;
import com.persagy.dmp.common.model.entity.BaseEntity;
import com.persagy.dmp.common.utils.JsonHelper;
import com.persagy.dmp.digital.client.DigitalObjectFacade;
import com.persagy.dmp.digital.entity.ObjectRelation;
import com.persagy.proxy.adm.annotations.CascadeColumn;
import com.persagy.proxy.adm.request.AdmQueryCriteria;
import com.persagy.proxy.adm.request.AdmResponse;
import com.persagy.proxy.adm.service.IAdmBaseService;
import com.persagy.proxy.adm.service.IAdmRelationService;
import com.persagy.proxy.adm.utils.AdmEntityTransferUtil;
import com.persagy.proxy.adm.utils.AdmQueryCriteriaHelper;
import com.persagy.proxy.common.client.DmpRwdClient;
import com.persagy.proxy.common.entity.DmpResult;
import com.persagy.proxy.common.entity.InstanceUrlParam;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * ADM 抽象实现类
 * @author Charlie Yu
 * @date 2021-08-16
 */
@Slf4j
public class AbstractAdmBaseServiceImpl<T> implements IAdmBaseService<T> {

    @Autowired
    private DmpRwdClient rwdClient;
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 查询
     * @param request
     * @return
     */
    @Override
    public AdmResponse doQuery(InstanceUrlParam context, AdmQueryCriteria request, Class<T> clazz) {
        // 转换为数据中台查询条件
        QueryCriteria dmpRequest = AdmQueryCriteriaHelper.toDmpCriteria(request);
        // 提供一个钩子方法用于处理已封装好的中台查询参数(因传参有buildingId而处理)
        processDmpCriteria(dmpRequest);
        // 转换参数
        JSONObject para = null;
        try {
            para = JsonHelper.toJsonObject(dmpRequest);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return AdmResponse.failure("参数格式有误");
        }
        log.info("request：{}，dmpRequest：{}， dmp-param: {}", request.toString(), dmpRequest.toString(), para.toString());
        // 调用中台查询
        DmpResult<JSONArray> dmpResult = rwdClient.queryObject(context, para);
        processResultAfterQuery(dmpResult);
        List<T> admVOs = AdmEntityTransferUtil.toAdmMultiEntity(dmpResult.getData(), request.getProjection(), clazz);
        //List<T> admVOs = AdmEntityTransferUtil.toAdmMultiEntityExtra(dmpResult.getData(), request.getProjection(), clazz);
        // 级联查询
        processCascade(context, admVOs, request.getName(), request.getCascade());
        // 设置返回值
        AdmResponse response = AdmResponse.success(admVOs);
        Long total = dmpResult.getCount() == null ? null : dmpResult.getCount().longValue();
        if(request.isOnlyCount()){
            response.setTotal(null);
            response.setCount(total);
        }else {
            response.setTotal(total);
        }
        response.setPageNumber(request.getPageNumber());
        response.setPageSize(request.getPageSize());
        return response;
    }
    /***
     * Description: 提供一个钩子方法用于处理已封装好的中台查询参数(因传参有buildingId而处理)
     * @param dmpRequest : 中台查询参数
     * @return : void
     * @author : lijie
     * @date :2021/10/28 20:23
     * Update By lijie 2021/10/28 20:23
     */
    protected void processDmpCriteria(QueryCriteria dmpRequest) {
        // 空方法,需要时子类重写
    }

    /**
     * 处理级联查询
     * @param context
     * @param admVOs
     * @param name
     * @param cascades
     */
    public void processCascade(InstanceUrlParam context, List admVOs, String name, List<AdmQueryCriteria> cascades) {
        if(CollUtil.isEmpty(admVOs) || CollUtil.isEmpty(cascades)) {
            return;
        }
        Map<String, Object> voMap = CollUtil.fieldValueMap(admVOs, BaseEntity.PROP_ID);
        Object simpleVO = admVOs.get(0);
        for(AdmQueryCriteria cascade:cascades) {
            if(StrUtil.isBlank(cascade.getName())){
                //处理 级联查询参数name首字母大写cascade": [ {"Name": "property"}]
                continue;
            }
            // 获取关联对象的Class
            Field casField = ReflectUtil.getField(simpleVO.getClass(), cascade.getName());
            if(casField == null) {
                continue;
            }
            // 获取级联信息
            CascadeColumn annotationInfo = casField.getAnnotation(CascadeColumn.class);
            if(annotationInfo == null) {
                continue;
            }
            Class clazz = getFieldRealClass(casField);
            // 获取关联对象的类型
            String objectType = getObjTypeByClass(clazz);
            if(StrUtil.isBlank(objectType)) {
                continue;
            }
            cascade.setName(objectType);
            // 转换为数据中台查询条件
            QueryCriteria dmpRequest = AdmQueryCriteriaHelper.toDmpCriteria(cascade);
            // 拼接关系条件，得到关系映射
            Map<String, List<String>> idMap = ensureRelationCond(context, dmpRequest.getCriteria(), voMap.keySet(), annotationInfo);
            if(MapUtil.isEmpty(idMap)) {
                continue;
            }
            // 查询结果
            List vos = queryByCondition(context, dmpRequest, cascade.getProjection(), clazz);
            if(CollUtil.isEmpty(vos)) {
                continue;
            }
            // 写回原对象中
            writeToVOs(voMap, idMap, vos, casField);
            // 递归继续处理
            processCascade(context, vos, objectType, cascade.getCascade());
        }
    }

    /**
     * 拼接关系条件
     * @param context
     * @param node
     * @param idList
     * @param annotationInfo
     * @return
     */
    private Map<String, List<String>> ensureRelationCond(InstanceUrlParam context, ObjectNode node, Set<String> idList, CascadeColumn annotationInfo) {
        if(CollUtil.isEmpty(idList)) {
            return null;
        }
        // 查询关系
        List<ObjectRelation> relations = queryRelations(context, idList, annotationInfo);
        ArrayNode array = node.putObject(BaseEntity.PROP_ID).putArray("$in");
        // 拼接查询条件
        if(CollUtil.isEmpty(relations)) {
            array.add("0");
        } else {
            List<String> relList = CollUtil.getFieldValues(relations, annotationInfo.selectToObj()?"objTo":"objFrom", String.class);
            relList.forEach(id -> array.add(id));
        }
        if(annotationInfo.selectToObj()){
            return groupMapList(relations, "objFrom", "objTo");
        }else{
            return groupMapList(relations, "objTo", "objFrom");
        }

    }

    /**
     * 将集合转换为Map
     * @param list 集合
     * @param keyField key属性名
     * @param valueField value属性名
     * @return
     */
    private static Map<String, List<String>> groupMapList(List<?> list, String keyField, String valueField) {
        if(CollUtil.isEmpty(list)) {
            return null;
        }
        Map<String, List<String>> groupMap = new HashMap<>();
        for(Object vo:list) {
            String key = (String) ReflectUtil.getFieldValue(vo, keyField);
            String value = (String) ReflectUtil.getFieldValue(vo, valueField);
            if(!StrUtil.isAllNotBlank(key, value)) {
                continue;
            }
            List<String> valueList = groupMap.get(key);
            if(CollUtil.isEmpty(valueList)) {
                valueList = new ArrayList<>();
                groupMap.put(key, valueList);
            }
            valueList.add(value);
        }
        return groupMap;
    }

    /**
     * 查询关系
     * @param context
     * @param idList
     * @param anno
     * @return
     */
    private List<ObjectRelation> queryRelations(InstanceUrlParam context, Set<String>idList, CascadeColumn anno) {
        // 如果没有任何配置，不处理
        if(StrUtil.isBlank(anno.graphCode()) && StrUtil.isBlank(anno.relCode()) &&
                StrUtil.isBlank(anno.relValue())) {
            return null;
        }
        // 查询关系
        QueryCriteria queryRequest = new QueryCriteria();
        ObjectNode criteria = JsonNodeFactory.instance.objectNode();
        queryRequest.setCriteria(criteria);
        boolean isSelectTo = anno.selectToObj();
        criteria.put(BaseEntity.PROP_VALID, ValidEnum.TRUE.getType());
        putString(criteria, "graphCode", anno.graphCode());
        putString(criteria, "relCode", anno.relCode());
        putString(criteria, "relValue", anno.relValue());
        ArrayNode array = criteria.putObject(isSelectTo?"objFrom":"objTo").putArray("$in");
        idList.forEach(id -> array.add(id));
        return SpringHelper.getBean(IAdmRelationService.class).queryByCondition(context, queryRequest);
    }

    /**
     * 添加字符串条件
     * @param node
     * @param key
     * @param value
     */
    private void putString(ObjectNode node, String key, String value) {
        if(StrUtil.isBlank(value)) {
            return;
        }
        node.put(key, value);
    }

    /**
     * 将子集写回到对象中
     * @param voMap 原始对象Map
     * @param idMap 主键映射<mainId, List<subId>>
     * @param vos 子对象
     * @param casField 子对象对应属性
     */
    private <V> void writeToVOs(Map<String, Object> voMap, Map<String, List<String>> idMap, List<V> vos, Field casField) {
        if(CollUtil.isEmpty(vos) || MapUtil.isEmpty(idMap)) {
            return;
        }
        // 转换为Map
        Map<String, V> subVOMap = CollUtil.fieldValueMap(vos, BaseEntity.PROP_ID);
        // 是否为集合
        boolean isList = List.class.isAssignableFrom(casField.getType());
        // 按对象循环
        for(String id:voMap.keySet()) {
            Object mainVO = voMap.get(id);
            List<String> subIdList = idMap.get(id);
            if(CollUtil.isEmpty(subIdList)) {
                continue;
            }
            // 如果是集合
            if(isList) {
                // 取出对应的对象集合
                Map<String, V> currVOMap = MapUtil.filter(subVOMap, subIdList.toArray(new String[0]));
                // 2021年11月11日17:30:04,by lijie.解决级联查询已经排好序的列表错乱问题
                List<V> currList = vos.stream().filter(vo -> currVOMap.containsKey(BeanUtil.getFieldValue(vo, BaseEntity.PROP_ID)))
                        .collect(Collectors.toList());
                // List<V> voList = CollUtil.newArrayList(currVOMap.values());
                ReflectUtil.setFieldValue(mainVO, casField, currList);
            } else {
                // 非集合取第一个即可
                String subId = subIdList.get(0);
                V vo = subVOMap.get(subId);
                ReflectUtil.setFieldValue(mainVO, casField, vo);
            }
        }
    }

    /**
     * 调用Client查询
     * @param context
     * @param criteria
     * @param projection
     * @param clazz
     * @return
     */
    private <V> List<V> queryByCondition(InstanceUrlParam context, QueryCriteria criteria, List<String> projection, Class<V> clazz) {
        // 转换参数
        if(criteria == null) {
            return null;
        }
        List<ObjectNode> objects = DigitalObjectFacade.query(context.getGroupCode(), context.getProjectId(),
                context.getAppId(), null, criteria);
        return AdmEntityTransferUtil.toAdmMultiEntity(objects, projection, clazz);
    }

    /**
     * 获取实际的Class
     * @param casField
     * @return
     */
    private Class getFieldRealClass(Field casField) {
        // 取到Field
        Class clazz = casField.getType();
        // 如果是集合，则取实际的对象
        if(List.class.isAssignableFrom(clazz)) {
            clazz = (Class) ((ParameterizedType) casField.getGenericType()).getActualTypeArguments()[0];
        }
        return clazz;
    }

    /**
     * 通过Class获取其对象类型
     * @param clazz
     * @return
     */
    private String getObjTypeByClass(Class clazz) {
        // 获取关联对象的类型
        String objectType = null;
        try {
            objectType = (String) ReflectUtil.getFieldValue(clazz.newInstance(), "objectType");
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return objectType;
    }

    /**
     * 新增
     * @param context
     * @param clazz
     * @param voList
     * @return
     */
    @Override
    public List<T> doInsert(InstanceUrlParam context, Class<T> clazz, List<T> voList) {
        if(CollUtil.isEmpty(voList)) {
            return null;
        }
        ArrayNode vos = AdmEntityTransferUtil.toDmpMultiEntity(voList);
        // 调用中台新增
        DmpResult<JSONArray> response = rwdClient.createObject(context, JSONArray.parseArray(vos.toString()));
        if(!DmpResult.SUCCESS.equals(response.getResult())) {
            throw new RuntimeException("调用中台查询接口出错：" + response.getMessage());
        }
        // 转换为结果
        // 2021年11月3日17:45:46 by lijie 新增一个钩子方法,用于处理floor的properties字段,不然转换为对象时会报转换异常
        processResultAfterQuery(response);
        return AdmEntityTransferUtil.toAdmMultiEntity(response.getData(), null, clazz);
    }
    /***
     * Description: 一个钩子方法,特殊处理一下楼层对象返回的properties信息点,不然转换为对象时会报转换异常
     * @param response : 查询结果
     * @return : void
     * @author : lijie
     * @date :2021/11/3 17:47
     * Update By lijie 2021/11/3 17:47
     */
    protected void processResultAfterQuery(DmpResult<JSONArray> response) {
    }

    /**
     * 修改
     * @param context
     * @param clazz
     * @param voList
     * @return
     */
    @Override
    public List<T> doUpdate(InstanceUrlParam context, Class<T> clazz, List<T> voList) {
        if(CollUtil.isEmpty(voList)) {
            return null;
        }
        ArrayNode vos = AdmEntityTransferUtil.toDmpMultiEntity(voList);
        // 调用中台修改
        DmpResult<JSONArray> response = rwdClient.updateObject(context, JSONArray.parseArray(vos.toString()));
        if(!DmpResult.SUCCESS.equals(response.getResult())) {
            throw new RuntimeException("调用中台查询接口出错：" + response.getMessage());
        }
        // 转换为结果
        processResultAfterQuery(response);
        return AdmEntityTransferUtil.toAdmMultiEntity(response.getData(), null, clazz);
    }

    /**
     * 删除
     * @param context
     * @param voList
     * @return
     */
    @Override
    public void doDelete(InstanceUrlParam context, List<T> voList) {
        if(CollUtil.isEmpty(voList)) {
            return;
        }
        List<String> idList = CollUtil.getFieldValues(voList, "id", String.class);
        // 调用中台修改
        DmpResult response = rwdClient.deleteObject(context, idList);
        if(!DmpResult.SUCCESS.equals(response.getResult())) {
            throw new RuntimeException("调用中台查询接口出错：" + response.getMessage());
        }
    }

    /**
     * 根据物理世界查询条件查询对象信息
     * @param context
     * @param dmpRequest
     * @param clazz
     * @return
     */
    @Override
    public AdmResponse doQuery(InstanceUrlParam context, QueryCriteria dmpRequest, Class<T> clazz) {
        // 转换参数
        JSONObject para = null;
        try {
            para = JsonHelper.toJsonObject(dmpRequest);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return null;
        }
        // 调用中台查询
        DmpResult<JSONArray> dmpResult = rwdClient.queryObject(context, para);
        processResultAfterQuery(dmpResult);
        List<T> admVOs = AdmEntityTransferUtil.toAdmMultiEntity(dmpResult.getData(), null, clazz);
//        JSONArray data = dmpResult.getData();
//        List<T> admVOs = new ArrayList<>();
//        if(CollUtil.isNotEmpty(data)){
//            admVOs = data.toJavaList(clazz);
//        }
        // 设置返回值
        AdmResponse response = AdmResponse.success(admVOs);
        Long total = dmpResult.getCount() == null ? null : dmpResult.getCount().longValue();
        response.setTotal(total);
        return response;
    }

    /**
     * 查询标记计算关系
     * @param context
     * @param dmpRequest
     * @param clazz
     * @return
     */
    @Override
    public AdmResponse doQueryRelationProjectCal(InstanceUrlParam context, QueryCriteria dmpRequest, Class<T> clazz) {
        // 转换参数
        JSONObject para = null;
        try {
            para = JsonHelper.toJsonObject(dmpRequest);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return null;
        }
        // 调用中台查询
        DmpResult<JSONArray> dmpResult = rwdClient.queryRelationProjectCal(context, para);
        List<T> admVOs = AdmEntityTransferUtil.toAdmMultiEntity(dmpResult.getData(), null, clazz);
        // 设置返回值
        AdmResponse response = AdmResponse.success(admVOs);
        Long total = dmpResult.getCount() == null ? null : dmpResult.getCount().longValue();
        response.setTotal(total);
        return response;
    }


    /**
     * 修改
     * @param context
     * @param clazz
     * @param voList
     * @return
     */
    @Override
    public List<T> doUpdateRelationProjectCal(InstanceUrlParam context, Class<T> clazz, List<T> voList) {
        if(CollUtil.isEmpty(voList)) {
            return null;
        }
        ArrayNode vos = AdmEntityTransferUtil.toDmpMultiEntity(voList);
        // 调用中台修改
        DmpResult<JSONArray> response = rwdClient.updateRelationProjectCal(context, JSONArray.parseArray(vos.toString()));
        if(!DmpResult.SUCCESS.equals(response.getResult())) {
            throw new RuntimeException("调用中台查询接口出错：" + response.getMessage());
        }
        // 转换为结果
        return AdmEntityTransferUtil.toAdmMultiEntity(response.getData(), null, clazz);
    }


    /**
     * 查询对象类型
     * @param request
     * @return
     */
    @Override
    public AdmResponse queryObjectClassCode(InstanceUrlParam context, AdmQueryCriteria request, Class<T> clazz) {
        // 转换为数据中台查询条件
        QueryCriteria dmpRequest = AdmQueryCriteriaHelper.toDmpCriteria(request);
        //重置分页，查询所有
        dmpRequest.setSize(null);
        // 提供一个钩子方法用于处理已封装好的中台查询参数(因传参有buildingId而处理)
        processDmpCriteria(dmpRequest);
        // 转换参数
        JSONObject para = null;
        try {
            para = JsonHelper.toJsonObject(dmpRequest);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return AdmResponse.failure("参数格式有误");
        }
        log.info("request：{}，dmpRequest：{}， dmp-param: {}", request.toString(), dmpRequest.toString(), para.toString());
        // 调用中台查询
        DmpResult<List<String>> dmpResult = rwdClient.queryObjectClassCode(context, para);
        // 设置返回值
        AdmResponse response = AdmResponse.success(dmpResult.getData());
        Long total = dmpResult.getCount() == null ? null : dmpResult.getCount().longValue();
        response.setTotal(total);
        return response;
    }
}
