/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved.
 * <a>http://www.crunii.com</a>
 */
package com.crunii.micro.domain.common.jdbc;

import com.alibaba.druid.pool.DruidDataSource;
import com.crunii.micro.common.constants.DSConstants;
import com.crunii.micro.common.dto.OrderProp;
import com.crunii.micro.common.exception.BusinessException;
import com.crunii.micro.common.utils.ConvertUtil;
import com.crunii.micro.common.utils.Slf4jUtil;
import com.crunii.micro.domain.common.jdbc.dto.AbsSetProps;
import com.crunii.micro.domain.common.jdbc.dto.DomainCol;
import com.crunii.micro.domain.common.jdbc.dto.DomainInfo;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.primitives.Ints;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.persistence.*;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author 田平 create 2020/11/27 16:50
 */
@Slf4j
public class DomainUtil {

	private static final String SET_PREFIX = "set";
	private static final String GET_PREFIX = "get";

	private static LoadingCache<Class<?>, DomainInfo> domainInfoCache = Caffeine.newBuilder().build(key -> getDomainInfo(key));

	public static DomainInfo getDomainInfoFromCache(Class<?> domainClass) {
		return domainInfoCache.get(domainClass);
	}

	private static DomainInfo getDomainInfo(Class<?> domainClass) {
		DomainInfo domainInfo = new DomainInfo();
		Table table = AnnotationUtils.findAnnotation(domainClass, Table.class);
		if (table == null) {
			throw new BusinessException("not find domain annotation Tab class:{}", domainClass.getName());
		}
		domainInfo.setDomainName(domainClass.getName());
		domainInfo.setTableName(table.name());
		domainInfo.setSchemaName(table.schema());

		Field[] fields = domainClass.getDeclaredFields();
		DomainCol primaryCol = null;
		for (Field field : fields) {
			Annotation[] as = field.getAnnotations();
			Column col = null;
			if (as != null && as.length > 0) {
				for (Annotation a : as) {
					if (a instanceof Column) {
						col = (Column) a;
						DomainCol domainCol = new DomainCol(field, col.name());
						domainInfo.getCols().put(field.getName(), domainCol);
						break;
					}
				}
				if (col != null && primaryCol == null) {
					for (Annotation a : as) {
						if (a instanceof Id) {
							primaryCol = new DomainCol();
							//主键的属性描述
							PropertyDescriptor primaryProp = null;
							try {
								primaryProp = new PropertyDescriptor(field.getName(), domainClass);
							} catch (IntrospectionException e) {
								throw new BusinessException("primaryProp initial error!", e);
							}
							domainInfo.setPrimaryProp(primaryProp);
							//主键列
							primaryCol.setColName(col.name());
							primaryCol.setField(field);
							//PostgresSql 自增长字段不能设置值
							for (Annotation annotation : as) {
								if (annotation instanceof GeneratedValue) {
									GeneratedValue v = (GeneratedValue) annotation;
									if (v.strategy() == GenerationType.IDENTITY) {
										primaryCol.setAutoIncrement(true);
										break;
									}
								}
							}
							//覆盖主键列
							domainInfo.getCols().put(primaryCol.getField().getName(), primaryCol);
							break;
						}
					}
				}
			}
		}
		if (primaryCol == null) {
			throw new BusinessException(Slf4jUtil.formatMsg("{} don't have primary key!", domainClass));
		}

		String tableName = domainInfo.getTableName();
		if (StringUtils.hasText(domainInfo.getSchemaName())) {
			tableName = domainInfo.getSchemaName() + "." + tableName;
		}

		domainInfo.setPrimaryCol(primaryCol);

		setSqlInfo(domainInfo, primaryCol, tableName);
		return domainInfo;
	}

	private static DruidDataSource createPgDruidDataSource() {
		String username = "scheduler";
		String password = "@Scheduler,.123";
		String url = "jdbc:postgresql://81.68.133.219:5432/scheduler?currentSchema=scheduler-job&autoReconnect=true";
		DruidDataSource ds = new DruidDataSource();
		ds.setUsername(username);
		ds.setPassword(password);
		ds.setUrl(url);
		return ds;
	}

	private static DruidDataSource createMyDruidDataSource() {
		String username = "scheduler";
		String password = "@Scheduler,.123";
		String url = "jdbc:mysql://81.68.133.219:3306/scheduler_local?useUnicode=true&characterEncoding=utf-8&useSSL=false";
		DruidDataSource ds = new DruidDataSource();
		ds.setUsername(username);
		ds.setPassword(password);
		ds.setUrl(url);
		return ds;
	}

	public static void main(String[] args) {
//		DruidDataSource ds = createPgDruidDataSource();
//		BaseNPJdbcTemplate jt = new BaseNPJdbcTemplate(ds, DSConstants.DATABASE_POSTGRESQL);
//
////		DruidDataSource ds = createMyDruidDataSource();
////		BaseNPJdbcTemplate jt = new BaseNPJdbcTemplate(ds, DSConstants.DATABASE_MYSQL);
//
//		List<EsShell> list = new ArrayList<>();
//		EsShell es = new EsShell();
//		es.setCreateTime(new Date());
//		es.setShellName("test");
//		es.setShellContent("ls");
//
//		EsShell es1 = new EsShell();
//		es1.setCreateTime(new Date());
//		es1.setShellName("test1");
//		es1.setShellContent("ls1");
//
//		list.add(es);
//		list.add(es1);
//		createDomains(jt, list, true);
//		//createDomain(jt, es);
//		ds.close();
	}

	private static void setSqlInfo(DomainInfo domainInfo, DomainCol primaryCol, String tableName) {
		String selectColSql = domainInfo.getCols()
				.entrySet()
				.stream()
				.map(entry -> entry.getValue().getColName() + " " + entry.getKey())
				.collect(Collectors.joining(","));

		domainInfo.setSelectColSql(selectColSql);

		domainInfo.setGetBaseSql("select " + selectColSql + " from " + tableName + " where ");

		domainInfo.setPageBaseSql("select " + selectColSql + " from " + tableName);
		domainInfo.setPageCountBaseSql("select count(*) from " + tableName);

		domainInfo.setGetByPrimarySql("select " + selectColSql + " from " + tableName + " where "
				+ primaryCol.getColName() + "=:" + primaryCol.getField().getName() + " ");

		domainInfo.setUpdateBaseSql("update " + tableName + " set ");

		domainInfo.setDeleteSql("delete from " + tableName + " where "
				+ primaryCol.getColName() + "=:" + primaryCol.getField().getName());

		StringBuilder sb = new StringBuilder();

		sb.append("insert into ").append(tableName)
				.append(domainInfo.getCols().values().stream().filter(c -> !c.isAutoIncrement())
						.map(c -> c.getColName())
						.collect(Collectors.joining(",", "(", ") ")))
				.append("values")
				.append(domainInfo.getCols().values().stream().filter(c -> !c.isAutoIncrement())
						.map(c -> ":" + c.getField().getName())
						.collect(Collectors.joining(",", "(", ") ")));
		domainInfo.setInsertSql(sb.toString());
	}

	/**
	 * 通过get，set的方法名获取Domain的属性名
	 *
	 * @param methodName
	 * @return
	 */
	public static String getPropName(String methodName) {
		if (methodName.startsWith(SET_PREFIX) || methodName.startsWith(GET_PREFIX)) {
			return toLowerCaseFirst(methodName.substring(3));
		} else {
			throw new BusinessException("methodName must start with set or get!");
		}
	}

	public static String toLowerCaseFirst(String str) {
		char[] cs = str.toCharArray();
		if (Character.isUpperCase(cs[0])) {
			cs[0] += 32;
			return String.valueOf(cs);
		} else {
			return str;
		}
	}

	public static String toUpperCaseFirst(String str) {
		char[] cs = str.toCharArray();
		if (Character.isLowerCase(cs[0])) {
			cs[0] -= 32;
			return String.valueOf(cs);
		} else {
			return str;
		}
	}

	public static <T extends AbsSetProps> int saveDomain(BaseNPJdbcTemplate npjt, T domain) {
		DomainInfo domainInfo = getDomainInfoFromCache(domain.getClass());
		if (domain.isDomainForUpdate()) {
			return updateDomain(npjt, domain, domainInfo);
		} else {
			return createDomain(npjt, domain, domainInfo);
		}
	}

	public static List<Integer> saveDomains(BaseNPJdbcTemplate npjt, List<? extends AbsSetProps> domains) {
		return saveDomains(npjt, domains, false);
	}

	public static List<Integer> saveDomains(BaseNPJdbcTemplate npjt, List<? extends AbsSetProps> domains, boolean useBatch) {
		if (!CollectionUtils.isEmpty(domains)) {
			DomainInfo domainInfo = getDomainInfoFromCache(domains.get(0).getClass());
			List<Integer> result = new ArrayList<>(domains.size());
			if (useBatch) {
				List<AbsSetProps> updates = new ArrayList<>();
				List<AbsSetProps> creates = new ArrayList<>();
				for (AbsSetProps domain : domains) {
					if (domain.isDomainForUpdate()) {
						updates.add(domain);
					} else {
						creates.add(domain);
					}
				}
				if (updates.size() > 0) {
					result.addAll(updateDomains(npjt, updates));
				}
				if (creates.size() > 0) {
					result.addAll(createDomains(npjt, creates));
				}
			} else {
				for (AbsSetProps domain : domains) {
					if (domain.isDomainForUpdate()) {
						result.add(updateDomain(npjt, domain, domainInfo));
					} else {
						result.add(createDomain(npjt, domain, domainInfo));
					}
				}
			}
			return result;
		} else {
			return Collections.emptyList();
		}
	}

	public static int deleteDomainById(BaseNPJdbcTemplate npjt, Class<? extends AbsSetProps> domainClass, Object id) {
		Assert.notNull(id, "primary id not allowed null");
		DomainInfo domainInfo = getDomainInfoFromCache(domainClass);
		Map<String, Object> props = new LinkedHashMap<>();
		props.put(domainInfo.getPrimaryCol().getField().getName(), ConvertUtil.convert(id, domainInfo.getPrimaryCol().getField().getType()));
		log.debug("deleteSql:{},domain:{}", domainInfo.getDeleteSql(), props);
		return npjt.update(domainInfo.getDeleteSql(), props);
	}

	public static int deleteDomainByIds(BaseNPJdbcTemplate npjt, Class<? extends AbsSetProps> domainClass, List<Object> ids) {
		return deleteDomainByIds(npjt, domainClass, ids, false);
	}

	public static int deleteDomainByIds(BaseNPJdbcTemplate npjt, Class<? extends AbsSetProps> domainClass, List<Object> ids, boolean useBatch) {
		if (!CollectionUtils.isEmpty(ids)) {
			DomainInfo domainInfo = getDomainInfoFromCache(domainClass);
			int size = 0;
			if (useBatch) {
				Map<String, Object>[] batchArgs = new Map[ids.size()];
				for (int i = 0; i < ids.size(); i++) {
					Object id = ids.get(i);
					Assert.notNull(id, "primary id not allowed null");
					Map<String, Object> param = new LinkedHashMap<>(1);
					param.put(domainInfo.getPrimaryCol().getField().getName(),
							ConvertUtil.convert(id, domainInfo.getPrimaryCol().getField().getType()));
					batchArgs[i] = param;
				}
				int[] rs = npjt.batchUpdate(domainInfo.getDeleteSql(), batchArgs);
				size = Arrays.stream(rs).sum();
			} else {
				for (Object id : ids) {
					size = size + deleteDomainById(npjt, domainClass, id);
				}
			}
			return size;
		} else {
			return 0;
		}
	}

	public static <T extends AbsSetProps> int deleteDomain(BaseNPJdbcTemplate npjt, T domain) {
		DomainInfo domainInfo = getDomainInfoFromCache(domain.getClass());
		return deleteDomain(npjt, domain, domainInfo);
	}

	public static List<Integer> deleteDomains(BaseNPJdbcTemplate npjt, List<? extends AbsSetProps> domains) {
		return deleteDomains(npjt, domains, false);
	}

	public static List<Integer> deleteDomains(BaseNPJdbcTemplate npjt, List<? extends AbsSetProps> domains, boolean useBatch) {
		if (!CollectionUtils.isEmpty(domains)) {
			DomainInfo domainInfo = getDomainInfoFromCache(domains.get(0).getClass());
			List<Integer> result = null;
			if (useBatch) {
				Map<String, Object>[] batchArgs = new Map[domains.size()];
				for (int i = 0; i < domains.size(); i++) {
					Map<String, Object> param = new LinkedHashMap<>(1);
					param.put(domainInfo.getPrimaryCol().getField().getName(), getIdValue(domains.get(i), domainInfo));
					batchArgs[i] = param;
				}
				int[] rs = npjt.batchUpdate(domainInfo.getDeleteSql(), batchArgs);
				//删除重置状态
				domains.stream().forEach(d -> ((AbsSetProps) d).setDomainForUpdate(false));
				result = Ints.asList(rs);
			} else {
				result = new ArrayList<>(domains.size());
				for (AbsSetProps domain : domains) {
					result.add(deleteDomain(npjt, domain, domainInfo));
				}
			}
			return result;
		} else {
			return Collections.emptyList();
		}
	}

	private static <T extends AbsSetProps> int deleteDomain(BaseNPJdbcTemplate npjt, T domain, DomainInfo domainInfo) {
		Map<String, Object> props = new LinkedHashMap<>();
		props.put(domainInfo.getPrimaryCol().getField().getName(), getIdValue(domain, domainInfo));
		log.debug("deleteSql:{},domain:{}", domainInfo.getDeleteSql(), props);
		int update = npjt.update(domainInfo.getDeleteSql(), domain);
		//删除重置状态
		domain.setDomainForUpdate(false);
		return update;
	}

	private static <T extends AbsSetProps> Object getIdValue(T domain, DomainInfo domainInfo) {
		Method read = domainInfo.getPrimaryProp().getReadMethod();
		Object id = null;
		try {
			id = read.invoke(domain);
		} catch (Exception e) {
			throw new BusinessException("primaryProp read error!", e);
		}
		return id;
	}

	private static <T extends AbsSetProps> void setIdValue(String dbType, T domain, DomainInfo domainInfo, KeyHolder holder) {
		if (DSConstants.DATABASE_MYSQL.equalsIgnoreCase(dbType)) {
			setIdValueFromObj(domain, domainInfo, holder.getKey());
		} else if (DSConstants.DATABASE_POSTGRESQL.equalsIgnoreCase(dbType)) {
			String colName = domainInfo.getPrimaryCol().getColName();
			Object value = holder.getKeyList().get(0).get(colName);
			setIdValueFromObj(domain, domainInfo, value);
		} else {
			throw new RuntimeException("unsupported this dbType:" + dbType);
		}
	}

	private static <T extends AbsSetProps> void setIdValueFromObj(T domain, DomainInfo domainInfo, Object value) {
		Method read = domainInfo.getPrimaryProp().getWriteMethod();
		try {
			Object id = ConvertUtil.convert(value, domainInfo.getPrimaryCol().getField().getType());
			read.invoke(domain, id);
		} catch (Exception e) {
			throw new BusinessException("primaryProp write error!", e);
		}
	}

	/**
	 * 返回影响的行数
	 *
	 * @param npjt
	 * @param domain
	 * @return
	 */
	public static <T extends AbsSetProps> int createDomain(BaseNPJdbcTemplate npjt, T domain) {
		DomainInfo domainInfo = getDomainInfoFromCache(domain.getClass());
		return createDomain(npjt, domain, domainInfo);
	}

	private static <T extends AbsSetProps> int createDomain(BaseNPJdbcTemplate npjt, T domain, DomainInfo domainInfo) {
		log.debug("insertSql:{},domain:{}", domainInfo.getInsertSql(), domain);
		if (domain.isDomainForUpdate()) {
			throw new BusinessException(Slf4jUtil.formatMsg("createDomain must construct domain domainForUpdate=false {}"
					, domainInfo.getDomainName()));
		}
		String dbType = npjt.getDbType();
		int rowSize = 0;
		if (domainInfo.getPrimaryCol().isAutoIncrement()) {
			KeyHolder keyHolder = new GeneratedKeyHolder();
			rowSize = npjt.update(domainInfo.getInsertSql(), domain, keyHolder);
			if (rowSize > 0) {
				//创建成功后，修改为更新对象
				domain.setDomainForUpdate(true);
				//设置自增长主键值
				setIdValue(dbType, domain, domainInfo, keyHolder);
			}
		} else {
			rowSize = npjt.update(domainInfo.getInsertSql(), domain);
			if (rowSize > 0) {
				//创建成功后，修改为更新对象
				domain.setDomainForUpdate(true);
			}
		}

		return rowSize;
	}

	public static List<Integer> createDomains(BaseNPJdbcTemplate npjt, List<? extends AbsSetProps> domains) {
		return createDomains(npjt, domains, false);
	}

	/**
	 * 返回每个影响的行数
	 *
	 * @param npjt
	 * @param domains
	 * @return
	 */
	public static List<Integer> createDomains(BaseNPJdbcTemplate npjt, List<? extends AbsSetProps> domains, boolean useBatch) {
		if (!CollectionUtils.isEmpty(domains)) {
			DomainInfo domainInfo = getDomainInfoFromCache(domains.get(0).getClass());
			String dbType = npjt.getDbType();
			List<Integer> result = null;
			if (useBatch) {
				SqlParameterSource[] batchArgs = new SqlParameterSource[domains.size()];
				for (int i = 0; i < domains.size(); i++) {
					SqlParameterSource sps = new BeanPropertySqlParameterSource(domains.get(i));
					batchArgs[i] = sps;
				}
				if (domainInfo.getPrimaryCol().isAutoIncrement()) {
					Class<?> type = domainInfo.getPrimaryCol().getField().getType();
					KeyHolder keyHolder = new GeneratedKeyHolder();
					int[] rs = npjt.batchInsert(domainInfo.getInsertSql(), batchArgs, type, keyHolder);
					if (DSConstants.DATABASE_MYSQL.equalsIgnoreCase(dbType)) {
						List<Object> values = new ArrayList<>(keyHolder.getKeys().values());
						for (int i = 0; i < values.size(); i++) {
							AbsSetProps domain = domains.get(i);
							domain.setDomainForUpdate(true);
							setIdValueFromObj(domain, domainInfo, values.get(i));
						}
					} else if (DSConstants.DATABASE_POSTGRESQL.equalsIgnoreCase(dbType)) {
						List<Map<String, Object>> keyList = keyHolder.getKeyList();
						Map<String, Object> vmap = keyList.get(0);
						for (int i = 0; i < domains.size(); i++) {
							AbsSetProps domain = domains.get(i);
							domain.setDomainForUpdate(true);
							setIdValueFromObj(domain, domainInfo, vmap.get(String.valueOf(i)));
						}
					} else {
						throw new RuntimeException("unsupported this dbType:" + dbType);
					}
					result = Ints.asList(rs);
				} else {
					int[] rs = npjt.batchUpdate(domainInfo.getInsertSql(), batchArgs);
					for (AbsSetProps domain : domains) {
						domain.setDomainForUpdate(true);
					}
					result = Ints.asList(rs);
				}
			} else {
				result = new ArrayList<>();
				for (AbsSetProps domain : domains) {
					result.add(createDomain(npjt, domain, domainInfo));
				}
			}
			return result;
		} else {
			return Collections.emptyList();
		}
	}

	public static <T extends AbsSetProps> int updateDomain(BaseNPJdbcTemplate npjt, T domain) {
		DomainInfo domainInfo = getDomainInfoFromCache(domain.getClass());
		return updateDomain(npjt, domain, domainInfo);
	}

	private static <T extends AbsSetProps> int updateDomain(BaseNPJdbcTemplate npjt, T domain, DomainInfo domainInfo) {
		if (domain.getDomainSetProps() == null) {
			throw new BusinessException("{} init must set domainForUpdate=true!", domainInfo.getDomainName());
		}
		if (!CollectionUtils.isEmpty(domain.getDomainSetProps())) {
			if (!domain.isDomainForUpdate()) {
				throw new BusinessException(Slf4jUtil.formatMsg("updateDomain must construct domain domainForUpdate=true {}"
						, domainInfo.getDomainName()));
			}
			String updateBaseSql = domainInfo.getUpdateBaseSql();
			StringBuilder sbSql = new StringBuilder(updateBaseSql);
			Set<Map.Entry<String, Object>> entries = domain.getDomainSetProps().entrySet();
			String setSql = entries.stream().map((Map.Entry<String, Object> entry) -> {
				DomainCol col = domainInfo.getCols().get(entry.getKey());
				return col.getColName() + "=:" + col.getField().getName();
			}).collect(Collectors.joining(",", " ", " "));

			sbSql.append(setSql);
			Method read = domainInfo.getPrimaryProp().getReadMethod();
			Object id = null;
			try {
				id = read.invoke(domain);
				domain.getDomainSetProps().put(domainInfo.getPrimaryCol().getField().getName(), id);
			} catch (Exception e) {
				throw new RuntimeException("primaryProp read error!", e);
			}
			sbSql.append("where ")
					.append(domainInfo.getPrimaryCol().getColName())
					.append("=:")
					.append(domainInfo.getPrimaryCol().getField().getName())
					.append(" ");

			log.debug("updateSql:{},props:{}", sbSql.toString(), domain.getDomainSetProps());
			int size = npjt.update(sbSql.toString(), domain.getDomainSetProps());
			//更新完成后清空修改属性map
			domain.resetDomainSetProps();
			return size;
		} else {
			return 0;
		}
	}

	public static List<Integer> updateDomains(BaseNPJdbcTemplate npjt, List<? extends AbsSetProps> domains) {
		if (!CollectionUtils.isEmpty(domains)) {
			DomainInfo domainInfo = getDomainInfoFromCache(domains.get(0).getClass());
			List<Integer> result = new ArrayList<>(domains.size());
			for (AbsSetProps domain : domains) {
				result.add(updateDomain(npjt, domain, domainInfo));
			}
			return result;
		} else {
			return Collections.emptyList();
		}
	}

	public static <T> T getOne(BaseNPJdbcTemplate npjt, Class<T> domainClass, Object id) {
		checkClass(domainClass);
		DomainInfo domainInfo = getDomainInfoFromCache(domainClass);
		Map<String, Object> props = new LinkedHashMap<>();
		props.put(domainInfo.getPrimaryCol().getField().getName(), id);
		log.debug("querySql:{},props:{}", domainInfo.getGetByPrimarySql(), props);
		T result = npjt.queryForBean(domainClass, domainInfo.getGetByPrimarySql(), props);
		return result;
	}

	/**
	 * 通过属性Map查找 props map中的value的类型必须和实体一致 ,只返回小于等于1条数据，多于一条异常
	 *
	 * @param npjt
	 * @param domainClass
	 * @param props
	 * @param <T>
	 * @return
	 */
	public static <T> T getUniqueByProps(BaseNPJdbcTemplate npjt, Class<T> domainClass, Map<String, Object> props) {
		List<T> list = findByProps(npjt, domainClass, props);
		if (list.size() == 1) {
			return list.get(0);
		} else if (list.size() == 0) {
			return null;
		} else {
			throw new IncorrectResultSizeDataAccessException(1, list.size());
		}
	}

	/**
	 * 通过属性返回唯一记录或者0条记录，0条记录时候返回空
	 *
	 * @param npjt
	 * @param domainClass
	 * @param prop
	 * @param value
	 * @param <T>
	 * @return
	 */
	public static <T> T getUniqueByProp(BaseNPJdbcTemplate npjt, Class<T> domainClass, String prop, Object value) {
		List<T> list = findByProp(npjt, domainClass, prop, value);
		if (list.size() == 1) {
			return list.get(0);
		} else if (list.size() == 0) {
			return null;
		} else {
			throw new IncorrectResultSizeDataAccessException(1, list.size());
		}
	}

	public static <T> List<T> findByProp(BaseNPJdbcTemplate npjt, Class<T> domainClass, String prop, Object value, OrderProp... os) {
		checkClass(domainClass);
		DomainInfo domainInfo = getDomainInfoFromCache(domainClass);
		StringBuilder sql = new StringBuilder(domainInfo.getGetBaseSql());
		DomainCol col = domainInfo.getCols().get(prop);
		if (col == null) {
			throw new BusinessException("{} not have prop:{}", domainClass.getName(), prop);
		} else {
			sql.append(col.getColName()).append("=:").append(col.getField().getName());
		}
		Map<String, Object> props = new LinkedHashMap<>(1);
		props.put(prop, value);

		sql.append(getOrderBySql(domainInfo, os));
		log.debug("querySql:{},props:{}", sql.toString(), props);
		return npjt.queryForBeans(domainClass, sql.toString(), props);
	}

	public static <T> List<T> findByProps(BaseNPJdbcTemplate npjt, Class<T> domainClass, Map<String, Object> props, OrderProp... os) {
		checkEmpty(props);
		checkClass(domainClass);
		DomainInfo domainInfo = getDomainInfoFromCache(domainClass);
		StringBuilder sql = new StringBuilder(domainInfo.getGetBaseSql());
		Set<Map.Entry<String, Object>> entries = props.entrySet();
		String condSql = entries.stream().map(entry -> {
			DomainCol col = domainInfo.getCols().get(entry.getKey());
			if (col == null) {
				log.warn("{} not have prop:{}", domainClass.getName(), entry.getKey());
				return null;
			} else {
				return col.getColName() + "=:" + col.getField().getName();
			}
		}).filter(s -> s != null).collect(Collectors.joining(" and "));

		sql.append(condSql);

		sql.append(getOrderBySql(domainInfo, os));
		log.debug("querySql:{},props:{}", sql.toString(), props);
		return npjt.queryForBeans(domainClass, sql.toString(), props);
	}

	private static String getOrderBySql(DomainInfo domainInfo, OrderProp... os) {
		if (ArrayUtils.isNotEmpty(os)) {
			String orderSql = Stream.of(os).map(op -> {
				DomainCol col = domainInfo.getCols().get(op.getProp());
				if (col == null) {
					log.warn("{} not have prop:{}", domainInfo.getDomainName(), op.getProp());
					return null;
				} else {
					if (op.isAscending()) {
						return col.getColName() + " asc ";
					} else {
						return col.getColName() + " desc ";
					}
				}
			}).filter(p -> p != null).collect(Collectors.joining(","));
			return " order by " + orderSql;
		} else {
			return "";
		}
	}

	private static void checkEmpty(Map<String, Object> props) {
		if (CollectionUtils.isEmpty(props)) {
			throw new BusinessException("props not allowed null or size 0!");
		}
	}

	private static void checkClass(Class<?> domainClass) {
		if (!AbsSetProps.class.isAssignableFrom(domainClass)) {
			throw new BusinessException("{} must extends AbstractSetProps!", domainClass.getName());
		}
	}

	/**
	 * 通过属性Map查找 props map中的value的类型自动转换
	 *
	 * @param npjt
	 * @param domainClass
	 * @param props
	 * @param os
	 * @param <T>
	 * @return
	 */
	public static <T> List<T> findByStrProps(BaseNPJdbcTemplate npjt, Class<T> domainClass, Map<String, String> props, OrderProp... os) {
		if (CollectionUtils.isEmpty(props)) {
			throw new BusinessException("props not allowed null or size 0!");
		} else {
			Map<String, Object> params = new LinkedHashMap<>(props.size());
			DomainInfo domainInfo = getDomainInfoFromCache(domainClass);
			props.entrySet().forEach(entry -> {
				DomainCol col = domainInfo.getCols().get(entry.getKey());
				if (col == null) {
					log.warn("{} not have prop:{}", domainInfo.getDomainName(), entry.getKey());
				} else {
					params.put(entry.getKey(), ConvertUtil.convert(entry.getValue(), col.getField().getType()));
				}
			});
			return findByProps(npjt, domainClass, params, os);
		}
	}

	/**
	 * 通过属性Map查找 props map中的value的类型自动转换 ,只返回小于等于1条数据，多于一条异常
	 *
	 * @param npjt
	 * @param domainClass
	 * @param props
	 * @param <T>
	 * @return
	 */
	public static <T> T getUniqueByStrProps(BaseNPJdbcTemplate npjt, Class<T> domainClass, Map<String, String> props) {
		List<T> list = findByStrProps(npjt, domainClass, props);
		if (list.size() == 1) {
			return list.get(0);
		} else if (list.size() == 0) {
			return null;
		} else {
			throw new IncorrectResultSizeDataAccessException(1, list.size());
		}
	}

}
