package cn.estsh.i3plus.pojo.base.jpa.daoimpl;

import cn.estsh.i3plus.pojo.base.common.Pager;
import cn.estsh.i3plus.pojo.base.enumutil.CommonEnumUtil;
import cn.estsh.i3plus.pojo.base.jpa.dao.BaseMongoRepository;
import cn.estsh.i3plus.pojo.base.codemaker.SnowflakeIdMaker;
import com.alibaba.fastjson.JSONObject;
import com.mongodb.BasicDBObject;
import com.mongodb.Block;
import com.mongodb.client.FindIterable;
import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Sorts;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.json.JsonMode;
import org.bson.json.JsonWriterSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;

import javax.persistence.Id;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

/**
 * @Description : 自定义mongodb持久化接口实现
 * @Reference :
 * @Author : alwaysfrin
 * @CreateDate : 2018-09-13 11:24
 * @Modify:
 **/
public class BaseMongoRepositoryImpl<T, ID extends Serializable> extends SimpleMongoRepository<T, ID>
                                                            implements BaseMongoRepository<T, ID> {
    public static final Logger LOGGER = LoggerFactory.getLogger(BaseMongoRepositoryImpl.class);

    /**
     * 持久化上下文
     */
    private final MongoOperations mongoOperations;
    private final MongoEntityInformation<T, ID> entityInformation;
    private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mongoContext;
    private final Class<T> entityClass;
    private SnowflakeIdMaker snowflakeIdMaker;

    public BaseMongoRepositoryImpl(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations,SnowflakeIdMaker snowflakeIdMaker) {
        super(metadata, mongoOperations);
        this.entityInformation = metadata;
        this.mongoOperations = mongoOperations;
        this.entityClass = this.entityInformation.getJavaType();
        mongoContext = mongoOperations.getConverter().getMappingContext();
        this.snowflakeIdMaker = snowflakeIdMaker;
    }

    /**
     * 重写新增方法
     * @param entity
     * @param <S>
     * @return
     */
    @Override
    public <S extends T> S insert(S entity) {
        innerSave(entity);
        return entity;
    }

    /**
     * 保存对象
     * @param item 保存对象
     * @return
     */
    private T innerSave(T item) {
        try {
            if(item==null)return null;
            Class<?> clazz = item.getClass();

            //获取主键
            Field idField = clazz.getField("id");
            if(idField == null){
                idField = clazz.getField("primaryKey");
            }
            if(idField == null){
                // 遍历所有属性，以@Id声明确认主键
                Field[] fields = clazz.getFields();
                for(Field f : fields){
                    if(f.getAnnotation(Id.class) != null){
                        idField = f;
                        break;
                    }
                }
            }

            if(idField != null){
                Class<?> type = idField.getType();
                Object val = idField.get(item);
                if((type == long.class || type == Long.class) && (val == null || Long.parseLong(val.toString()) == 0)){
                    // long类型主键，以snowflake为主键
                    idField.set(item, snowflakeIdMaker.nextId() + System.currentTimeMillis());
                } else if(type == String.class && (val==null || "".equals(val))){
                    // String类型主键，以UUID为主键
                    idField.set(item, UUID.randomUUID().toString().replace("-", "").toLowerCase());
                }
            }

//            try {
                this.mongoOperations.insert(item, this.entityInformation.getCollectionName());
//            }catch (DuplicateKeyException dke){
//                LOGGER.error("【出现重复主键】");
//                //出现重复主键，再次插入
//                if(idField != null){
//                    Class<?> type = idField.getType();
//                    Object val = idField.get(item);
//                    if((type == long.class || type == Long.class) && (val == null || Long.parseLong(val.toString()) == 0)){
//                        // long类型主键，以snowflake为主键
//                        idField.set(item, snowflakeIdMaker.nextId());
//                    } else if(type == String.class && (val==null || "".equals(val))){
//                        // String类型主键，以UUID为主键
//                        idField.set(item, UUID.randomUUID().toString().replace("-", "").toLowerCase());
//                    }
//                }
//
//                this.mongoOperations.insert(item, this.entityInformation.getCollectionName());
//            }
            if(idField != null){
                return item;
            }else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Override
    public Optional<T> findById(ID id) {
        T entity = this.mongoOperations.findById(id, this.entityInformation.getJavaType(), this.entityInformation.getCollectionName());

        if(entity != null) {
            return Optional.of(entity);
        }else{
            return null;
        }
    }

    @Override
    public T getById(long id) {
        return this.mongoOperations.findById(id, this.entityInformation.getJavaType(), this.entityInformation.getCollectionName());
    }

    @Override
    public List<T> findByProperty(String propertyName, Object value) {
        return findByProperty(propertyName,value,null,0);
    }

    @Override
    public List<T> findByProperty(String propertyName, Object value, String orderByParam, int ascOrDesc) {
        Bson bson = Filters.and(
                Filters.eq(propertyName, value)
        );

        return findByBson(bson,orderByParam,ascOrDesc);
    }

    @Override
    public List<T> findByPropertyLike(String propertyName, Object value) {
        return findByPropertyLike(propertyName,value,null,0);
    }

    @Override
    public List<T> findByPropertyLike(String propertyName, Object value, String orderByParam, int ascOrDesc) {
        Bson bson = Filters.and(
                Filters.regex(propertyName, value.toString())
        );

        return findByBson(bson,orderByParam,ascOrDesc);
    }

    @Override
    public T getByProperty(String propertyName, Object value) {
        List<T> tList = this.findByProperty(propertyName,value);
        if(tList.size() > 0){
            return tList.iterator().next();
        }else{
            return null;
        }
    }

    /**
     * 根据bson查询
     * @param bson
     * @return
     */
    public List<T> findByBson(Bson bson) {
        return findByBson(bson,null,0);
    }

    @Override
    public List<T> findByBson(Bson bson, String orderByParam, int ascOrDesc) {
        List<Document> dList = new ArrayList<>();
        Block<Document> saveBlock = new Block<Document>() {
            @Override
            public void apply(final Document document) {
                dList.add(document);
            }
        };

        FindIterable findIter = null;
        if(bson != null) {
            findIter = mongoOperations.getCollection(this.entityInformation.getCollectionName()).find(bson);
        }else{
            findIter = mongoOperations.getCollection(this.entityInformation.getCollectionName()).find();
        }
        if(StringUtils.isNotBlank(orderByParam) && ascOrDesc != 0){
            //排序
            if(ascOrDesc == CommonEnumUtil.ASC_OR_DESC.ASC.getValue()){
                findIter.sort(Sorts.ascending(orderByParam));
            }else{
                findIter.sort(Sorts.descending(orderByParam));
            }
        }
        findIter.forEach(saveBlock);

        return packObjectListFromDocument(dList);
    }

    @Override
    public int findByBsonCount(Bson bson) {
        if(bson == null){
            return (int) mongoOperations.getCollection(this.entityInformation.getCollectionName()).count();
        }else {
            return (int) mongoOperations.getCollection(this.entityInformation.getCollectionName()).count(bson);
        }
    }

    private List<T> packObjectListFromDocument(List<Document> dList) {
        //将获取的document转为对象
        List<T> resultList = new ArrayList<>();
        // 设置为宽松模式
        JsonWriterSettings jsonWriterSettings = JsonWriterSettings.builder().outputMode(JsonMode.RELAXED).build();
        for(Document d : dList){
            resultList.add(JSONObject.parseObject(d.toJson(jsonWriterSettings), entityClass));
        }

        return resultList;
    }

    @Override
    public List<T> findByBsonPager(Bson bson, Pager pager) {
        return findByBsonPager(bson,pager,null,0);
    }

    @Override
    public List<T> findByBsonPager(Bson bson, Pager pager, String orderByParam, int ascOrDesc) {
        List<Document> dList = new ArrayList<>();
        Block<Document> saveBlock = new Block<Document>() {
            @Override
            public void apply(final Document document) {
                dList.add(document);
            }
        };

        FindIterable findIter = null;
        if(bson == null) {
            findIter = mongoOperations.getCollection(this.entityInformation.getCollectionName()).find();
        }else{
            findIter = mongoOperations.getCollection(this.entityInformation.getCollectionName()).find(bson);
        }
        if(StringUtils.isNotBlank(orderByParam) && ascOrDesc != 0){
            //排序
            if(ascOrDesc == CommonEnumUtil.ASC_OR_DESC.ASC.getValue()){
                findIter.sort(Sorts.ascending(orderByParam));
            }else{
                findIter.sort(Sorts.descending(orderByParam));
            }
        }else{
            //根据id排序
            findIter.sort(new BasicDBObject("_id", 1));
        }
        findIter.skip(pager.getStartRow()).limit(pager.getPageSize());
        findIter.forEach(saveBlock);

        return packObjectListFromDocument(dList);
    }

    @Override
    public int listCount() {
        return (int) mongoOperations.getCollection(this.entityInformation.getCollectionName()).count();
    }

    @Override
    public List<T> listPager(Pager pager) {
        return listPager(pager,null,0);
    }

    @Override
    public List<T> listPager(Pager pager, String orderByParam, int ascOrDesc) {
        List<Document> dList = new ArrayList<>();
        Block<Document> saveBlock = new Block<Document>() {
            @Override
            public void apply(final Document document) {
                dList.add(document);
            }
        };
        FindIterable findIter = mongoOperations.getCollection(this.entityInformation.getCollectionName()).find();
        if(StringUtils.isNotBlank(orderByParam) && ascOrDesc != 0){
            //排序
            if(ascOrDesc == CommonEnumUtil.ASC_OR_DESC.ASC.getValue()){
                findIter.sort(Sorts.ascending(orderByParam));
            }else{
                findIter.sort(Sorts.descending(orderByParam));
            }
        }else{
            //根据id排序
            findIter.sort(new BasicDBObject("_id", 1));
        }
        findIter.skip(pager.getStartRow()).limit(pager.getPageSize());
        findIter.forEach(saveBlock);
        return packObjectListFromDocument(dList);
    }

    @Override
    public long deleteByProperty(Bson bson) {
        return mongoOperations.getCollection(this.entityInformation.getCollectionName()).deleteMany(bson).getDeletedCount();
    }

    @Override
    public long deleteByPropertyIn(String propName, Object[] propValues) {
        Bson bson = Filters.and(
                Filters.in(propName, propValues)
        );
        return deleteByProperty(bson);
    }

    @Override
    public long deleteByIds(ID[] ids) {
        return deleteByPropertyIn("_id",ids);
    }

}
