package com.tykj.workflowcore.model_layer.service.impl;

import com.github.wenhao.jpa.PredicateBuilder;
import com.github.wenhao.jpa.Specifications;
import com.tykj.workflowcore.base.result.ApiException;
import com.tykj.workflowcore.model_layer.annotations.WorkFlowCoreNoScan;
import com.tykj.workflowcore.model_layer.dao.ColumnInfoDao;
import com.tykj.workflowcore.model_layer.dao.TableInfoDao;
import com.tykj.workflowcore.model_layer.entity.TableInfo;
import com.tykj.workflowcore.model_layer.entity.vo.*;
import com.tykj.workflowcore.model_layer.entity.*;
import com.tykj.workflowcore.model_layer.myEnum.ModelType;
import com.tykj.workflowcore.model_layer.service.ModelService;

import com.tykj.workflowcore.model_layer.utils.SessionUtil;
import com.tykj.workflowcore.model_layer.utils.SqlUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Session;

import org.hibernate.internal.SessionImpl;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.IntegerType;
import org.hibernate.type.StringType;
import org.hibernate.type.TimestampType;
import org.hibernate.type.Type;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;


import javax.persistence.Entity;
import javax.persistence.EntityManagerFactory;


import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.criteria.*;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

import static com.tykj.workflowcore.model_layer.utils.ClassTypeLength.setLength;

import static com.tykj.workflowcore.model_layer.utils.CreateTableUtil.*;
import static com.tykj.workflowcore.model_layer.utils.HqlUtil.createQuery;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;


/**
 * @ClassName ModelImpl
 * @Description TODO
 * @Author WWW
 * @Date 2021/2/26 13:39
 * @Version 1.0
 */

@Service
@Slf4j
public class ModelImpl implements ModelService {


    @Autowired
    private TableInfoDao tableInfoDao;

    @Autowired
    private ColumnInfoDao columnInfoDao;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private SessionUtil sessionUtil;

    /**
     * @param
     * @return java.util.List<com.tykj.workflowcore.model_layer.model.TableInfo>
     * @Author WWW
     * @Description 得到所有表
     * @Date 16:14 2021/3/5
     **/
    @Override
    public Page<TableInfo> listAllEntities(SearchTableInfoVo searchTableInfoVo) {
        PredicateBuilder<TableInfo> and = Specifications.and();
        and.like(searchTableInfoVo.getModelName() != null && StringUtils.isNotEmpty(searchTableInfoVo.getModelName()), "modelName", "%" + searchTableInfoVo.getModelName() + "%");
        and.like(searchTableInfoVo.getModelTitle() != null && StringUtils.isNotEmpty(searchTableInfoVo.getModelTitle()), "modelTitle", "%" + searchTableInfoVo.getModelTitle() + "%");
        and.in(searchTableInfoVo.getModelType() != null&&searchTableInfoVo.getModelType().length>0, "modelType", searchTableInfoVo.getModelType());
        return tableInfoDao.findAll(and.build(), searchTableInfoVo.getPageable());

    }

    /**
     * @param searchColumnInfoVo
     * @return java.util.List<com.tykj.workflowcore.model_layer.model.ColumnInfo>
     * @Author WWW
     * @Description 根据表名得到所有字段名
     * @Date 16:15 2021/3/5
     **/
    @Override
    public List<ColumnInfo> showModelFields(SearchColumnInfoVo searchColumnInfoVo) {
        PredicateBuilder<ColumnInfo> and = Specifications.and();
        and.eq(searchColumnInfoVo.getDbId() != null, "dbId", searchColumnInfoVo.getDbId());
        and.eq(searchColumnInfoVo.getDbName() != null && StringUtils.isNotEmpty(searchColumnInfoVo.getDbName()), "dbName", searchColumnInfoVo.getDbName());
        return columnInfoDao.findAll(and.build());
    }


    /**
     * @param tableVO
     * @return com.tykj.workflowcore.model_layer.model.TableVO
     * @Author WWW
     * @Description 完全新建一张表
     * @Date 16:16 2021/3/5
     **/
    @Override
    public TableInfo newTable(TableVO tableVO) {
        String xmlMapping = createTable(tableVO);
        Integer modelType = tableVO.getModelType();
        String parentTable = null;
        //扫描新建类型
        if (modelType.equals(ModelType.BASICS_EXTENSION)) {
            parentTable = tableVO.getParentTable();
            tableVO.setModelType(ModelType.BASICS_EXTENSION);
        } else {
            tableVO.setModelType(ModelType.BUSINESS);
        }


        sessionUtil.addXml(xmlMapping);
        Session session = sessionUtil.getSession();


        List<ColumnVO> dataList = tableVO.getDataList();
        TableInfo tableInfo = new TableInfo();
        tableInfo.setModelName(tableVO.getModelName());
        tableInfo.setModelTitle(tableVO.getModelTitle());
        tableInfo.setXml(xmlMapping);
        tableInfo.setModelType(tableVO.getModelType());
        tableInfo.setDescription(tableVO.getDescription());
        tableInfo.setParentTable(parentTable);
        tableInfoDao.save(tableInfo);
        //默认存储ID字段
        ColumnInfo cId = new ColumnInfo(0, "id", "主键", "java.lang.Integer", 11, tableInfo.getModelName(), tableInfo.getId(), "主键");
        columnInfoDao.save(cId);
        for (ColumnVO columnVO : dataList) {
            ColumnInfo columnInfo = new ColumnInfo();
            columnInfo.setFieldName(columnVO.getFieldName());
            columnInfo.setFieldType(columnVO.getFieldType());
            columnInfo.setFieldLength(columnVO.getFieldLength());
            columnInfo.setFieldTitle(columnVO.getFieldTitle());
            columnInfo.setDescription(columnVO.getDescription());
            columnInfo.setDbName(tableInfo.getModelName());
            columnInfo.setDbId(tableInfo.getId());
            columnInfoDao.save(columnInfo);
        }

        //关闭会话
        session.close();
        return tableInfo;
    }

    /**
     * @param map
     * @return int
     * @Author WWW
     * @Description 根据表名新增数据
     * @Date 16:17 2021/3/5
     **/
    @Override
    public int putValueByEntityName(Map<String, Object> map) {
        for (String tableName :
                map.keySet()) {
            //查找对应的表
            Specification spec = (root, criteriaQuery, criteriaBuilder) -> {
                Predicate equal;
                Path name = root.get("modelName");
                equal = criteriaBuilder.equal(name, tableName);
                return equal;
            };
            Optional one = tableInfoDao.findOne(spec);
            TableInfo tableInfo = null;
            if (one.isPresent()) {
                tableInfo = (TableInfo) one.get();
            }
            Integer modelType = tableInfo.getModelType();
            if (modelType.equals(ModelType.BUSINESS)) {
                Object values = map.get(tableName);
                if (values instanceof Map) {
                    //插入数据
                    insertValue(tableInfo.getModelName(), (Map) values);
                } else {
                    //循环插入数据
                    List valuesList = (List) values;
                    for (int i = 0; i < valuesList.size(); i++) {
                        insertValue(tableInfo.getModelName(), (Map) valuesList.get(i));
                    }
                }
            } else {
                return 1;
            }

        }
        return 0;
    }

    /**
     * @param tableName
     * @param map
     * @return void
     * @Author WWW
     * @Description 新增参数的方法
     * @Date 16:17 2021/3/5
     **/
    public void insertValue(String tableName, Map map) {


        Session newSession = sessionUtil.getSession();

        SessionImpl session = (SessionImpl) newSession;
        EntityPersister entityPersister = session.getEntityPersister(tableName, map);
        Type[] propertyTypes = entityPersister.getPropertyTypes();
        String[] propertyNames = entityPersister.getEntityPersister().getPropertyNames();
        Object[] propertyValuesToInsert = entityPersister.getPropertyValuesToInsert(map, null, session);
        for (int i = 0; i < propertyValuesToInsert.length; i++) {
            Object value = propertyValuesToInsert[i];
            Type propertyType = propertyTypes[i];
            //先将Type转为java类
            if (propertyType instanceof TimestampType) {
                try {
                    Date parse = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse((String) value);
                    map.put(propertyNames[i], parse);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }
            if (propertyType instanceof IntegerType) {
                //然后调用强转方法
                int i1 = Integer.valueOf(value + "");
                map.put(propertyNames[i], i1);

            }
            if (propertyType instanceof StringType) {
                //然后调用强转方法
                map.put(propertyNames[i], value);
            }
        }
        newSession.saveOrUpdate(tableName, map);
        newSession.getTransaction().begin();
        newSession.getTransaction().commit();
        newSession.close();
    }

    /**
     * 扫描表
     *
     * @param classList
     */
    @Override
    public void swaggerScan(List<Class<?>> classList) {
        /* 扫描前先删除原数据*/
        delTableAndColumnInfo();
        for (Class<?> aClass : classList) {
            //不扫描自己
            TableInfo tableInfo = new TableInfo();
            TableVO tableVO = new TableVO();
            //入表真实名称
            String realName = "";
            if (!aClass.isAnnotationPresent(WorkFlowCoreNoScan.class)) {
                if (aClass.isAnnotationPresent(Entity.class)) {
                    if (aClass.isAnnotationPresent(Table.class)) {
                        Table table = aClass.getAnnotation(Table.class);
                        if (!"".equals(table.name()) && table.name() != null) {
                            realName = table.name();
                        }
                    } else {
                        String className = getClassName(aClass.toString());
                        realName = className;
                    }
                    tableVO.setModelName(realName);
                    //获得类中文描述
                    if (aClass.isAnnotationPresent(ApiModel.class)) {
                        ApiModel annotation = aClass.getAnnotation(ApiModel.class);
                        StringBuilder apiModelDocument = new StringBuilder();
                        if (annotation.value() != null && !"".equals(annotation.value())) {
                            apiModelDocument.append(annotation.value() + "|");
                        }
                        if (annotation.description() != null && !"".equals(annotation.description())) {
                            apiModelDocument.append(annotation.description() + "|");
                        }
                        tableVO.setModelTitle(apiModelDocument.toString());
                    } else {
                        tableVO.setModelTitle("(无描述)");
                    }
                    //获得类所有属性
                    Field[] declaredFields = aClass.getDeclaredFields();

                    java.lang.reflect.Type genericType;
                    List<ColumnVO> list = new ArrayList<>();
                    for (Field declaredField : declaredFields) {
                        ColumnVO columnVO = new ColumnVO();
                        if (declaredField.isAnnotationPresent(Transient.class)) {
                            continue;
                        }
                        //获得类型
                        genericType = declaredField.getGenericType();
                        //是否主键
                        if (declaredField.isAnnotationPresent(javax.persistence.Id.class)) {
                            columnVO.setPrimaryKey(0);
                        }

                        columnVO.setFieldType(getTypeName(genericType.toString()));
                        columnVO.setFieldName(getClassName(declaredField.toString()));
                        columnVO.setFieldLength(setLength(genericType.toString()));
                        //获得属性中文描述
                        if (declaredField.isAnnotationPresent(ApiModelProperty.class)) {
                            ApiModelProperty annotation = declaredField.getAnnotation(ApiModelProperty.class);
                            StringBuilder apiModelPropertyDocument = new StringBuilder();
                            if (annotation.value() != null && !"".equals(annotation.value())) {
                                apiModelPropertyDocument.append(annotation.value() + "|");
                            }
                            if (annotation.example() != null && !"".equals(annotation.example())) {

                                apiModelPropertyDocument.append(annotation.example() + "|");
                            }
                            columnVO.setFieldTitle(apiModelPropertyDocument.toString());
                        } else {
                            columnVO.setFieldTitle("(无描述)");
                        }
                        list.add(columnVO);
                    }
                    tableVO.setDataList(list);
                    String xml = createTable(tableVO);
                    tableInfo.setModelName(tableVO.getModelName());
                    tableInfo.setModelTitle(tableVO.getModelTitle());
                    tableInfo.setXml(xml);
                    tableInfo.setModelType(ModelType.BASICS);
                    //判断是否存在
                    if (checkRepeat(realName)) {
                        tableInfo = tableInfoDao.save(tableInfo);
                    } else {
                        tableInfo = tableInfoDao.findByModelName(tableInfo.getModelName());
                        log.info("{}已存在", realName);
                    }
                    List<ColumnVO> dataList = tableVO.getDataList();
                    for (ColumnVO columnVO : dataList) {
                        ColumnInfo columnInfo = new ColumnInfo();
                        columnInfo.setFieldName(columnVO.getFieldName());
                        columnInfo.setFieldType(columnVO.getFieldType());
                        columnInfo.setFieldLength(columnVO.getFieldLength());
                        columnInfo.setFieldTitle(columnVO.getFieldTitle());
                        columnInfo.setPrimaryKey(columnVO.getPrimaryKey());
                        columnInfo.setDbName(realName);
                        columnInfo.setDbId(tableInfo.getId());
                        columnInfoDao.save(columnInfo);
                    }
                }
            }
        }
    }

    /**
     * @param tableName
     * @return boolean
     * @Author WWW
     * @Description 判重
     * @Date 10:50 2021/3/11
     **/
    public boolean checkRepeat(String tableName) {
        Specification spec = (root, criteriaQuery, criteriaBuilder) -> {
            Path name = root.get("modelName");
            Predicate equal = criteriaBuilder.equal(name, tableName);
            return equal;
        };
        List<TableInfo> all = tableInfoDao.findAll(spec);

        for (TableInfo tableInfo : all) {
            String name = tableInfo.getModelName();
            if (tableName.equals(name)) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param name
     * @return java.util.List
     * @Author WWW
     * @Description 通过表名查询所有信息
     * @Date 10:51 2021/3/11
     **/
    @Override
    public List<Map<String, Object>> findAllByName(String name) {
        if (name != null && name != "") {
            String sql = "select * from " + name;
            return jdbcTemplate.queryForList(sql);
        }
        return null;
    }

    @Override
    public List<Map<String, Object>> complexQuery(String tableName, List<QueryCondition> queryConditions) {

        if (!"".equals(tableName)) {
            String query = createQuery(tableName, queryConditions);
            return jdbcTemplate.queryForList(query);
        }
        return null;
    }

    @Override
    public List<TableInfo> listAllEntities() {

        return tableInfoDao.findAll();
    }

    @Override
    public int updateTable(UpdateTableInfoVO updateTableInfoVO) {
        //tableInfo和columnInfo变化
        //查询到TableInfo和ColumnInfo
        TableVO tableVO = updateTableInfoVO.getTableVO();
        Integer dbId = updateTableInfoVO.getDbId();
        TableInfo tableInfo = tableInfoDao.findById(dbId).orElseThrow(() -> new RuntimeException("未找到该id的表信息"));
        Integer modelType = tableInfo.getModelType();
        if (modelType.equals(ModelType.BUSINESS)) {
            tableInfo.setUpdatedTime(new Date());

            tableInfo.setDescription(tableVO.getDescription());
            tableInfo.setModelTitle(tableVO.getModelTitle());
            tableInfo.setModelType(tableVO.getModelType());
            String xml = createTable(tableVO);
            //重新存xml
            tableInfo.setXml(xml);
            tableInfoDao.save(tableInfo);

            //原来的字段信息
            Specification spec = (Specification) (root, criteriaQuery, criteriaBuilder) -> {
                Predicate predicate = criteriaBuilder.equal(root.get("dbId"), dbId);
                return predicate;
            };
            List<ColumnInfo> originalColumnInfos = columnInfoDao.findAll(spec);
            //新的字段信息
            List<ColumnInfo> currentColumnInfos = tableVO.getDataList().stream()
                    .map(columnVO -> columnInfo(tableInfo.getId(), tableInfo.getModelName(), columnVO))
                    .collect(Collectors.toList());

            //根据ColumnInfo集合得出表实际改动的sql语句集合
            List<String> sqls = getTableSqls(tableInfo.getModelName(), originalColumnInfos, currentColumnInfos);
            //执行sql语句
            for (String sql : sqls) {
                System.out.println(sql);
                jdbcTemplate.execute(sql);
            }

            //重新保存字段信息
            List<ColumnInfo> columnsForAdd = getColumnsForAdd(originalColumnInfos, currentColumnInfos);
            for (ColumnInfo columnInfo : columnsForAdd) {
                columnInfoDao.save(columnInfo);
            }
            List<ColumnInfo> columnsForUpdate = getColumnsForUpdate(originalColumnInfos, currentColumnInfos);
            for (ColumnInfo columnInfo : columnsForUpdate) {
                columnInfoDao.save(columnInfo);
            }
            List<ColumnInfo> columnsFordDelete = getColumnsFordDelete(originalColumnInfos, currentColumnInfos);
            for (ColumnInfo originColumnInfo : columnsFordDelete) {
                columnInfoDao.delete(originColumnInfo);
            }

        }
        return 1;
    }

    /**
     * 对象类型转换
     * ColumnVo -> ColumnInfo
     */
    private ColumnInfo columnInfo(Integer dbId, String dbName, ColumnVO columnVO) {
        ColumnInfo columnInfo = new ColumnInfo();
        columnInfo.setId(columnVO.getId());
        columnInfo.setFieldName(columnVO.getFieldName());
        columnInfo.setFieldType(columnVO.getFieldType());
        columnInfo.setFieldLength(columnVO.getFieldLength());
        columnInfo.setFieldTitle(columnVO.getFieldTitle());
        columnInfo.setFieldLength(columnVO.getFieldLength());
        columnInfo.setDescription(columnVO.getDescription());
        columnInfo.setDbId(dbId);
        columnInfo.setDbName(dbName);
        return columnInfo;
    }

    /**
     * 根据ColumnInfo集合得出表实际改动的sql语句集合
     * 根据ColumnInfo的id来匹配字段变化
     */
    private List<String> getTableSqls(String table, List<ColumnInfo> origin, List<ColumnInfo> current) {
        List<String> result = new ArrayList<>();
        for (ColumnInfo columnInfo : current) {
            ColumnInfo originColumnInfo = origin.stream()
                    .filter(columnInfo1 -> Objects.equals(columnInfo.getId(), columnInfo1.getId()))
                    .findAny()
                    .orElse(null);
            if (isNull(originColumnInfo)) {
                String sql = SqlUtil.addColumn(table, columnInfo.getFieldName(), columnInfo.getFieldType(), columnInfo.getFieldLength());
                result.add(sql);
            } else {
                String sql = SqlUtil.updateColumn(table, originColumnInfo.getFieldName(), columnInfo.getFieldName(), columnInfo.getFieldType(), columnInfo.getFieldLength());
                result.add(sql);
            }
        }
        for (ColumnInfo originColumnInfo : origin) {
            boolean noneMatch = current.stream()
                    .noneMatch(columnInfo1 -> Objects.equals(originColumnInfo.getId(), columnInfo1.getId()));
            if (noneMatch) {
                String sql = SqlUtil.deleteColumn(table, originColumnInfo.getFieldName());
                result.add(sql);
            }
        }
        return result;
    }

    private List<ColumnInfo> getColumnsForAdd(List<ColumnInfo> origin, List<ColumnInfo> current) {
        List<ColumnInfo> result = new ArrayList<>();
        for (ColumnInfo columnInfo : current) {
            ColumnInfo originColumnInfo = origin.stream()
                    .filter(columnInfo1 -> Objects.equals(columnInfo.getId(), columnInfo1.getId()))
                    .findAny()
                    .orElse(null);
            if (isNull(originColumnInfo)) {
                result.add(columnInfo);
            }
        }
        return result;
    }

    private List<ColumnInfo> getColumnsForUpdate(List<ColumnInfo> origin, List<ColumnInfo> current) {
        List<ColumnInfo> result = new ArrayList<>();
        for (ColumnInfo columnInfo : current) {
            ColumnInfo originColumnInfo = origin.stream()
                    .filter(columnInfo1 -> Objects.equals(columnInfo.getId(), columnInfo1.getId()))
                    .findAny()
                    .orElse(null);
            if (nonNull(originColumnInfo)) {
                result.add(columnInfo);
            }
        }
        return result;
    }

    private List<ColumnInfo> getColumnsFordDelete(List<ColumnInfo> origin, List<ColumnInfo> current) {
        List<ColumnInfo> result = new ArrayList<>();
        for (ColumnInfo originColumnInfo : origin) {
            boolean noneMatch = current.stream()
                    .noneMatch(columnInfo1 -> Objects.equals(originColumnInfo.getId(), columnInfo1.getId()));
            if (noneMatch) {
                result.add(originColumnInfo);
            }
        }
        return result;
    }

    @Override
    public int delTable(DelTableVO delTableVO) {
        Optional<TableInfo> byId = tableInfoDao.findById(delTableVO.getId());
        if (!byId.isPresent()) {
            throw new ApiException("此id已经被删除！");
        } else {
            TableInfo tableInfo = byId.get();
            Integer modelType = tableInfo.getModelType();
            if (modelType.equals(ModelType.BUSINESS)) {
                tableInfoDao.delete(tableInfo);
                List<ColumnInfo> allByDbId = columnInfoDao.findAllByDbId(delTableVO.getId());
                columnInfoDao.deleteInBatch(allByDbId);
                jdbcTemplate.execute("drop  table  " + tableInfo.getModelName());

                return 1;
            }
        }
        return 0;
    }


    @Override
    public TableAndColumnInfoVO getTableInfoAndColumnInfoByBatch(Integer[] ids) {
        TableAndColumnInfoVO tableAndColumnInfoVO = new TableAndColumnInfoVO();
        PredicateBuilder<TableInfo> builder1 = Specifications.and();
        builder1.in(ids != null, "id", ids);
        List<TableInfo> allTableInfos = tableInfoDao.findAll(builder1.build());

        PredicateBuilder<ColumnInfo> builder2 = Specifications.and();
        builder2.in(ids != null, "dbId", ids);
        List<ColumnInfo> allColumnInfos = columnInfoDao.findAll(builder2.build());

        tableAndColumnInfoVO.setTableInfos(allTableInfos);
        tableAndColumnInfoVO.setColumnInfos(allColumnInfos);
        return tableAndColumnInfoVO;
    }

    public void delTableAndColumnInfo() {
        /* 先删除上次扫描的基础类型*/
        List<TableInfo> tableInfoList = tableInfoDao.findAllByModelType(ModelType.BASICS);
        for (TableInfo tableInfo : tableInfoList) {
            /*删除columnInfo*/
            columnInfoDao.deleteAllByDbId(tableInfo.getId());
        }
    }
}
