package com.hotent.base.manager.impl;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Resource;

import com.baomidou.mybatisplus.annotation.DbType;
import com.hotent.base.constants.SQLConst;
import com.hotent.base.datasource.DatabaseContext;
import com.hotent.base.util.AppUtil;
import com.hotent.base.util.string.StringPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hotent.base.dao.CommonDao;
import com.hotent.base.exception.BaseException;
import com.hotent.base.exception.SystemException;
import com.hotent.base.manager.CommonManager;
import com.hotent.base.manager.QueryFilterHelper;
import com.hotent.base.query.PageBean;
import com.hotent.base.query.PageList;
import com.hotent.base.query.QueryFilter;
import com.hotent.base.util.BeanUtils;
import com.hotent.base.util.StringUtil;

/**
 * 通用管理器的默认实现
 * 
 * @company 广州宏天软件股份有限公司
 * @author heyifan
 * @email heyf@jee-soft.cn
 * @date 2018年4月25日
 */
@Service
public class DefaultCommonManager implements CommonManager, QueryFilterHelper<Map<String, Object>> {
	private final Logger logger = LoggerFactory.getLogger(getClass());
	
	public static final int DEFAULT_BATCH_SIZE = 1000; 
	
	@Resource
	CommonDao commonDao;

    @Resource
    DatabaseContext databaseContext;

    @Override
	public int execute(String sql) {
		Assert.notNull(sql, "sql can not be empty.");
		return this.execute(sql, new HashMap<>());
	}

	/**
	 * 将参数化sql和值转换为map型
	 * <p>
	 * 该方法返回修改后的sql，传入的map会被清空后将objs按顺序放入map，sql转化后示例： select * from ... where a=?
	 * and b=? 转换为 select * from ... where a=#{key0} and b=#{key1}
	 * </p>
	 * 
	 * @param map
	 * @param sql
	 * @param objs
	 */
	private String convertToMap(Map<String, Object> map, String sql, Object... objs) {
		Assert.notNull(map, "map不能为空");
		Assert.isTrue(StringUtil.isNotEmpty(sql), "sql不能为空");
		Assert.isTrue(BeanUtils.isNotEmpty(objs), "objs不能为空");
		Pattern pattern = Pattern.compile("(\\?)");
		Matcher matcher = pattern.matcher(sql);
		StringBuffer sb = new StringBuffer();
		int count = 0;
		while (matcher.find()) {
			matcher.appendReplacement(sb, String.format("#{key%s}", count));
			count++;
		}
		matcher.appendTail(sb);

		if (count != objs.length) {
			throw new BaseException(String.format("sql：%s中参数化的数量和传递进来的参数：%s数量不匹配。", sql, objs));
		}
		map.clear();
		for (int i = 0; i < objs.length; i++) {
			map.put("key" + i, objs[i]);
		}
		return sb.toString();
	}

	@Override
	public int execute(String sql, Object... objs) {
		Map<String, Object> map = new HashMap<>();
		String newSql = convertToMap(map, sql, objs);
		return this.execute(newSql, map);
	}

	@Override
	public int execute(String sql, Map<String, Object> map) {
		map.put("sql", sql);
		return commonDao.update(map);

	}

	@Override
	public List<Map<String, Object>> query(String sql) {
		return this.query(sql, new HashMap<>());
	}

	@Override
	public List<Map<String, Object>> query(String sql, Object... objs) {
		Map<String, Object> map = new HashMap<>();
		String newSql = convertToMap(map, sql, objs);
		return this.query(newSql, map);
	}

	@Override
	public List<Map<String, Object>> query(String sql, Map<String, Object> map) {
		Assert.notNull(sql, "sql can not be empty.");
		Assert.notNull(map, "map can not be empty.");
		if (map.containsKey("sql")) {
			logger.warn(
					"can not define 'sql' in map when invoke CommonManager.query(sql, map), it will be override by sql string.");
		}
		map.put("sql", sql);
		return commonDao.query(map);
		
	}

	@Override
	public PageList<Map<String, Object>> query(String sql, PageBean pageBean) {
		Assert.notNull(sql, "sql can not be empty.");
		IPage<Map<String, Object>> result = commonDao.queryByPage(convert2IPage(pageBean), sql);
		return new PageList<Map<String, Object>>(result);
	}

	@Override
	public PageList<Map<String, Object>> query(String sql, QueryFilter<?> queryFilter) throws SystemException {
		Assert.notNull(sql, "sql can not be empty.");
		Assert.notNull(queryFilter, "queryFilter can not be empty.");
		PageBean pageBean = queryFilter.getPageBean();
		IPage<Map<String, Object>> result = commonDao.queryByQueryFilter(convert2IPage(pageBean), sql,
				convert2Wrapper(queryFilter, null));
		return new PageList<Map<String, Object>>(result);
	}

	@Override
	public PageList<Map<String, Object>> queryByCustomSql(String sql, QueryFilter<?> queryFilter)
			throws SystemException {
		Assert.notNull(sql, "sql can not be empty.");
		Assert.notNull(queryFilter, "queryFilter can not be empty.");
		PageBean pageBean = queryFilter.getPageBean();
		IPage<Map<String, Object>> result = commonDao.queryByCustomSql(convert2IPage(pageBean), sql,
				convert2Wrapper(queryFilter, null));
		return new PageList<Map<String, Object>>(result);
	}
	
	@Override
	public int batchInsert(String tableName, List<Map<String,Object>> valueList, int batchSize) {
        String dbType = databaseContext.getDbType();
        long currentTimeMillis = System.currentTimeMillis();
		int size = valueList.size();
        int i = 1;
        int update = 0;
        List<Map<String,Object>> group = new ArrayList<Map<String,Object>>();
		Map<String, Object> stringObjectMap = valueList.get(0);
		Set<String> fieldNames = stringObjectMap.keySet();
		StringJoiner sj = new StringJoiner(StringPool.COMMA);
		fieldNames.stream().forEach(fieldName->{
			sj.add(fieldName);
		});
		String insertFields = sj.toString();
        for (Map<String,Object> element : valueList) {
        	if(BeanUtils.isEmpty(element)) {
        		continue;
        	}
        	group.add(element);
            if ((i % batchSize == 0) || i == size) {
                if(SQLConst.DB_ORACLE.equals(dbType)){
                    update += commonDao.batchInsert4Oracle(tableName, insertFields, group);
                }else {
                    update += commonDao.batchInsert(tableName, insertFields, group);
                }
                group.clear();
            }
            i++;
        }
        if(BeanUtils.isNotEmpty(group)) {
            int result = 0;
            if(SQLConst.DB_ORACLE.equals(dbType)){
                result += commonDao.batchInsert4Oracle(tableName, insertFields, group);
            }else {
                result += commonDao.batchInsert(tableName, insertFields, group);
            }
        	if(result == 1) {
        		update += group.size();
        	}
        }
        logger.info("批量插入{}表{}条数据耗时{}", tableName,update, (System.currentTimeMillis() - currentTimeMillis));
		return update;
	}

	@Override
	public int batchInsert(String tableName, List<Map<String,Object>> valueList) {
		return batchInsert(tableName, valueList,DEFAULT_BATCH_SIZE);
	}
	
	@Override
	public int batchUpdate(String tableName, String pkField, List<Map<String, Object>> valueMap, int batchSize) {
		return batchUpdate(tableName, pkField, valueMap, batchSize, false);
	}

	@Override
	public int batchUpdate(String tableName, String pkField, List<Map<String, Object>> valueList) {
		return batchUpdate(tableName, pkField, valueList,DEFAULT_BATCH_SIZE);
	}

	@Override
	public int batchDelete(String tableName, String pkField, List<String> batchParams, int batchSize) {
		Assert.hasText(pkField, "pkField must not empty");
		int size = batchParams.size();
        int i = 1;
        List<String> group = new ArrayList<String>();
        for (String pkValue : batchParams) {
        	if(StringUtil.isEmpty(pkValue)) {
        		continue;
        	}
        	group.add(pkValue);
            if ((i % batchSize == 0) || i == size) {
            	commonDao.batchDelete(tableName, pkField, group);
                group.clear();
            }
            i++;
        }
        if(BeanUtils.isNotEmpty(group)) {
        	commonDao.batchDelete(tableName, pkField, group);
        }
		return batchParams.size();
	}

	@Override
	public int batchDelete(String tableName, String pkField, List<String> batchParams) {
		return batchDelete(tableName, pkField, batchParams, DEFAULT_BATCH_SIZE);
	}

	@Override
	public int updateByCustom(String tableName, String pkField, Map<String, ?> map) {
		return commonDao.updateByCustom(tableName, pkField, map);
	}

	@Override
	public int batchUpdate(String tableName, String pkField, List<Map<String, Object>> valueMap,
			Boolean ignoreDataVersion) {
		return batchUpdate(tableName, pkField, valueMap, DEFAULT_BATCH_SIZE, ignoreDataVersion);
	}

	@Override
	public int batchUpdate(String tableName, String pkField, List<Map<String, Object>> valueMap, int batchSize,
			Boolean ignoreDataVersion) {
		DatabaseContext bean = AppUtil.getBean(DatabaseContext.class);
		String dbType = bean.getDbType();
		boolean supportBatch = true;
		if (DbType.ORACLE.getDb().equals(dbType)) {
			supportBatch = false;
		}
		long currentTimeMillis = System.currentTimeMillis();
		int size = valueMap.size();
		int i = 1;
		List<Map<String, Object>> group = new ArrayList<Map<String, Object>>();
		int update = 0;
		for (Map<String, Object> element : valueMap) {
			if (BeanUtils.isEmpty(element)) {
				continue;
			}
			group.add(element);
			if ((i % batchSize == 0) || i == size) {
				int result = (supportBatch || i == 1) ? commonDao.batchUpdate(tableName, pkField, group, ignoreDataVersion) : singleUpdate(tableName, pkField, group, ignoreDataVersion);
				if (result == 1 || result == -1) {
					update += group.size();
				}
				group.clear();
			}
			i++;
		}
		if (BeanUtils.isNotEmpty(group)) {
			int result = (supportBatch || i == 1) ? commonDao.batchUpdate(tableName, pkField, group, ignoreDataVersion) : singleUpdate(tableName, pkField, group, ignoreDataVersion);
			if (result == 1) {
				update += group.size();
			}
		}
		logger.info("批量更新{}表{}条数据耗时{}", tableName, update, (System.currentTimeMillis() - currentTimeMillis));
		return update;
	}

	// todo: oracle执行多条语句有问题，比如不支持多条语句（用;隔开的）,虽然可以用pl/sql来执行，但不支持获取更新的条数（语句块返回值只有-1）
	private int singleUpdate(String tableName, String pkField, List<Map<String, Object>> group, boolean ignoreDataVersion) {
		int result = 0;
		if (BeanUtils.isEmpty(group)) {
			return result;
		}
		for (int i = 0; i < group.size(); i++) {
			result += commonDao.batchUpdate(tableName, pkField, group.subList(i, i + 1), ignoreDataVersion);
		}
		return result;
	}

	@Override
	public List<Map<String, Object>> queryByCustomSqlNoPage(String sql, QueryFilter<?> queryFilter)
			throws SystemException {
		Assert.notNull(sql, "sql can not be empty.");
		Assert.notNull(queryFilter, "queryFilter can not be empty.");
		List<Map<String, Object>> result = commonDao.queryByCustomSql( sql,
				convert2Wrapper(queryFilter, null));
		return result;
	}

}
