package cd.casic.framework.mongo.core.impl;

import cd.casic.framework.mongo.core.entity.Page;
import cd.casic.framework.mongo.core.sdk.MongoService;
import cd.casic.framework.mongo.core.wrapper.LambdaQueryWrapper;
import cd.casic.framework.mongo.utils.ClassUtils;
import cd.casic.framework.mongo.utils.QueryBuildUtils;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

@SuppressWarnings("all")
public class MongoServiceImpl<T> implements MongoService<T> {

    private final Class<T> targetClass = (Class<T>) ClassUtils.getClass(this);

    @Autowired
    protected MongoTemplate mongoTemplate;

    @Override
    public T getOne(LambdaQueryWrapper<T> queryWrapper) {
        Query query = QueryBuildUtils.buildQuery(queryWrapper);
        return mongoTemplate.findOne(query, targetClass);
    }

    @Override
    public boolean save(T entity) {
        return Objects.nonNull(mongoTemplate.save(entity));
    }

    @Override
    public boolean saveBatch(Collection<T> entityList) {
        mongoTemplate.insertAll(entityList);
        return true;
    }

    @Override
    public boolean removeById(Serializable id) {

        Criteria criteria = Criteria.where("_id").is(id);
        Query query = new Query(criteria);
        DeleteResult deleteResult = mongoTemplate.remove(query, targetClass);
        return deleteResult.getDeletedCount() > 0;
    }

    @Override
    public long remove(LambdaQueryWrapper<T> queryWrapper) {
        Query query = QueryBuildUtils.buildQuery(queryWrapper);
        DeleteResult deleteResult = mongoTemplate.remove(query, targetClass);
        return deleteResult.getDeletedCount();
    }

    @Override
    public boolean updateById(T entity) {

        Criteria criteria = Criteria.where("_id").is(ClassUtils.getId(entity));
        Query query = new Query(criteria);
        Update update = getUpdate(entity);
        UpdateResult updateResult = mongoTemplate.updateFirst(query,update, targetClass);
        return updateResult.getModifiedCount() > 0;
    }

    private Update getUpdate(T entity){

        Update update = new Update();
        for (Field field : entity.getClass().getDeclaredFields()){
            try {
                field.setAccessible(true);
                Object result = field.get(entity);
                if (Objects.nonNull(result)){
                    update.set(field.getName(), result);
                }
            } catch (Exception e) {

            }
        }
        return update;
    }

    @Override
    public long update(T entity, LambdaQueryWrapper<T> queryWrapper) {

        Query query = QueryBuildUtils.buildQuery(queryWrapper);
        Update update = getUpdate(entity);
        UpdateResult updateResult = mongoTemplate.updateFirst(query,update,targetClass);
        return updateResult.getModifiedCount();
    }

    @Override
    public long update(Update update, LambdaQueryWrapper<T> queryWrapper) {
        Query query = QueryBuildUtils.buildQuery(queryWrapper);
        UpdateResult updateResult = mongoTemplate.updateFirst(query,update,targetClass);
        return updateResult.getModifiedCount();
    }

    @Override
    public boolean upsert(T entity, LambdaQueryWrapper<T> queryWrapper) {

        Query query  = QueryBuildUtils.buildQuery(queryWrapper);
        Update update = getUpdate(entity);
        UpdateResult updateResult = mongoTemplate.upsert(query,update,targetClass);
        return updateResult.getModifiedCount() > 0;
    }

    @Override
    public T getById(Serializable id) {

        Criteria criteria = Criteria.where("_id").is(id);
        Query query = new Query(criteria);
        return mongoTemplate.findOne(query, targetClass);
    }

    @Override
    public long count(LambdaQueryWrapper<T> queryWrapper) {

        Query query = QueryBuildUtils.buildQuery(queryWrapper);
        return mongoTemplate.count(query, targetClass);
    }

    @Override
    public List<T> list(LambdaQueryWrapper<T> queryWrapper) {

        Query query = QueryBuildUtils.buildQuery(queryWrapper);
        return mongoTemplate.find(query, targetClass);
    }

    @Override
    public Page page(LambdaQueryWrapper<T> queryWrapper, int pageNum, int pageSize) {

        Query query = QueryBuildUtils.buildQuery(queryWrapper);
        Page<T> page = new Page<>();
        page.setPageNum(pageNum);
        page.setPageSize(pageSize);
        long total = mongoTemplate.count(query, targetClass);
        page.setTotal(total);
        if (total <= 0){
            return page;
        }
        query.skip((long) (pageNum - 1) * pageSize).limit(pageSize);
        List<T> list = mongoTemplate.find(query, targetClass);
        page.setRecords(list);
        return page;
    }

    @Override
    public boolean exist(LambdaQueryWrapper<T> queryWrapper) {

        Query query = QueryBuildUtils.buildQuery(queryWrapper);
        return mongoTemplate.exists(query, targetClass);
    }

    @Override
    public Collection<T> listByIds(Collection<? extends Serializable> idList) {

        Criteria criteria = Criteria.where("_id").in(idList);
        Query query = new Query(criteria);
        return mongoTemplate.find(query, targetClass);
    }
}
