package cn.ibizlab.core.data.service.impl;

import cn.ibizlab.core.data.dto.BaseData;
import cn.ibizlab.core.data.dto.FilterData;
import cn.ibizlab.core.data.mapper.DbDataMapper;
import cn.ibizlab.core.data.model.DSLink;
import cn.ibizlab.core.data.model.POSchema;
import cn.ibizlab.core.data.service.IPersistentService;
import cn.ibizlab.core.data.service.ModelService;
import cn.ibizlab.util.errors.BadRequestAlertException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Primary
@Service
@Slf4j
public class DbPersistentServiceImpl implements IPersistentService {

    @Value("${ibiz.data.batchsize:500}")
    private int BATCH_SIZE;

    @Autowired
    private DbDataMapper repository;
    @Autowired
    @Lazy
    private ModelService modelService;
    
    @Override
    public BaseData create(DSLink link, POSchema poSchema, BaseData et, boolean bGet) {
        repository.insertData(link.getName(),poSchema,et);
        if(bGet) et.setAll(getByMap(link,poSchema,et));
        return et;
    }

    @Override
    public List<BaseData> createBatch(DSLink link, POSchema poSchema, List<BaseData> list, boolean bGet) {
        List<BaseData> batch=new ArrayList<>();
        list.forEach(et->{
            batch.add(et);
            if(batch.size()>=BATCH_SIZE)
            {
                repository.insertBathData(link.getName(),poSchema,batch);
                batch.clear();
            }
        });
        if(batch.size()>0)
        {
            repository.insertBathData(link.getName(),poSchema,batch);
            batch.clear();
        }
        if(bGet) return getByMapBatch(link,poSchema,list);
        return list;
    }

    @Override
    public BaseData update(DSLink link, POSchema poSchema, BaseData et, boolean bGet) {
        repository.updateData(link.getName(),poSchema,et);
        if(bGet) et.setAll(getByMap(link,poSchema,et));
        return et;
    }

    @Override
    public List<BaseData> updateBatch(DSLink link, POSchema poSchema, List<BaseData> list, boolean bGet) {
        List<BaseData> batch=new ArrayList<>();
        list.forEach(et->{
            batch.add(et);
            if(batch.size()>=BATCH_SIZE)
            {
                repository.updateBathData(link.getName(),poSchema,batch);
                batch.clear();
            }
        });
        if(batch.size()>0)
        {
            repository.updateBathData(link.getName(),poSchema,batch);
            batch.clear();
        }
        if(bGet) return getByMapBatch(link,poSchema,list);
        return list;
    }

    @Override
    public boolean remove(DSLink link, POSchema poSchema, Serializable key) {
        BaseData et=poSchema.newData(key);
        Assert.notNull(et,"未找到主键");
        repository.removeData(link.getName(),poSchema,et);
        return true;
    }

    @Override
    public boolean removeBatch(DSLink link, POSchema poSchema, List<Serializable> idList) {
        List<BaseData> batch=new ArrayList<>();
        idList.forEach(key->{
            BaseData et = poSchema.newData(key);
            Assert.notNull(et,"未找到主键");
            batch.add(et);
            if(batch.size()>=BATCH_SIZE)
            {
                repository.removeBathData(link.getName(),poSchema,batch);
                batch.clear();
            }
        });
        if(batch.size()>0)
        {
            repository.removeBathData(link.getName(),poSchema,batch);
            batch.clear();
        }
        return true;
    }

    @Override
    public boolean removeByMap(DSLink link, POSchema poSchema, BaseData et) {
        repository.removeData(link.getName(),poSchema,et);
        return true;
    }

    @Override
    public boolean removeByMapBatch(DSLink link, POSchema poSchema, List<BaseData> list) {
        List<BaseData> batch=new ArrayList<>();
        list.forEach(et->{
            batch.add(et);
            if(batch.size()>=BATCH_SIZE)
            {
                repository.removeBathData(link.getName(),poSchema,batch);
                batch.clear();
            }
        });
        if(batch.size()>0)
        {
            repository.removeBathData(link.getName(),poSchema,batch);
            batch.clear();
        }
        return true;
    }

    @Override
    public BaseData get(DSLink link, POSchema poSchema, Serializable key) {
        BaseData et=poSchema.newData(key);
        Assert.notNull(et,"未找到主键");
        List<BaseData> list = repository.getData(link.getName(),poSchema,et);
        if(ObjectUtils.isEmpty(list)||list.size()>1)
            throw new IllegalArgumentException("未找到数据"+poSchema.getName()+":"+key);
        return list.get(0);
    }

    @Override
    public List<BaseData> getBatch(DSLink link, POSchema poSchema, List<Serializable> idList) {
        List<BaseData> rt=new ArrayList<>();
        List<BaseData> batch=new ArrayList<>();
        idList.forEach(key->{
            BaseData et = poSchema.newData(key);
            Assert.notNull(et,"未找到主键");
            batch.add(et);
            if(batch.size()>=BATCH_SIZE)
            {
                rt.addAll(repository.getBatchData(link.getName(),poSchema,batch));
                batch.clear();
            }
        });
        if(batch.size()>0)
        {
            rt.addAll(repository.getBatchData(link.getName(),poSchema,batch));
            batch.clear();
        }
        return rt;
    }

    @Override
    public BaseData getByMap(DSLink link, POSchema poSchema, BaseData et) {
        List<BaseData> list = repository.getData(link.getName(),poSchema,et);
        if(ObjectUtils.isEmpty(list)||list.size()>1)
            throw new IllegalArgumentException("未找到数据"+poSchema.getName()+":"+et.getKey());
        return list.get(0);
    }

    @Override
    public List<BaseData> getByMapBatch(DSLink link, POSchema poSchema, List<BaseData> list) {
        List<BaseData> rt=new ArrayList<>();
        List<BaseData> batch=new ArrayList<>();
        list.forEach(et->{
            batch.add(et);
            if(batch.size()>=BATCH_SIZE)
            {
                rt.addAll(repository.getBatchData(link.getName(),poSchema,batch));
                batch.clear();
            }
        });
        if(batch.size()>0)
        {
            rt.addAll(repository.getBatchData(link.getName(),poSchema,batch));
            batch.clear();
        }
        return rt;
    }

    @Override
    public List<BaseData> getBatchKey(DSLink link, POSchema poSchema, List<BaseData> list) {
        List<BaseData> rt=new ArrayList<>();
        List<BaseData> batch=new ArrayList<>();
        list.forEach(et->{
            batch.add(et);
            if(batch.size()>=BATCH_SIZE)
            {
                rt.addAll(repository.getBatchKey(link.getName(),poSchema,batch));
                batch.clear();
            }
        });
        if(batch.size()>0)
        {
            rt.addAll(repository.getBatchKey(link.getName(),poSchema,batch));
            batch.clear();
        }
        return rt;
    }

    @Override
    public Long count(DSLink link, POSchema poSchema, BaseData et) {
        return repository.countData(link.getName(),poSchema,et);
    }

    @Override
    public boolean checkKey(DSLink link, POSchema poSchema, BaseData et) {
        Long cnt=repository.countData(link.getName(),poSchema,et);
        return cnt==1;
    }

    @Override
    public BaseData save(DSLink link, POSchema poSchema, BaseData et, boolean bGet) {
        if(checkKey(link,poSchema,et))
            return update(link,poSchema,et,bGet);
        else
            return create(link,poSchema,et,bGet);
    }

    @Override
    public List<BaseData> saveBatch(DSLink link, POSchema poSchema, List<BaseData> list, boolean bGet) {
        List<BaseData> rt=this.getBatchKey(link,poSchema,list);

        Map<Serializable,Integer> keys=new LinkedHashMap<>();
        rt.forEach(et->{
            Serializable key=poSchema.getKeyValue(et,false);
            if(!ObjectUtils.isEmpty(key))
                keys.put(key,1);
        });

        List<BaseData> create=new ArrayList<>();
        List<BaseData> update=new ArrayList<>();

        list.forEach(et-> {
            Serializable key = poSchema.getKeyValue(et, false);
            if(keys.containsKey(key))
                update.add(et);
            else
                create.add(et);
        });
        List rtList=new ArrayList<>();
        if(update.size()>0)
            rtList.addAll(this.updateBatch(link,poSchema,update,bGet));
        if(create.size()>0)
            rtList.addAll(this.createBatch(link,poSchema,create,bGet));

        return rtList;
    }

    @Override
    public List<BaseData> select(DSLink link, POSchema poSchema, BaseData et) {
        return repository.selectData(link.getName(),poSchema,et);
    }

    @Override
    public Page<BaseData> select(DSLink link, POSchema poSchema, BaseData et, Pageable pageable) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<BaseData> pages=
                repository.selectData(link.getName(),poSchema,et,FilterData.getPages(poSchema,pageable));
        return new PageImpl<BaseData>(pages.getRecords(), pageable, pages.getTotal());
    }

    @Override
    public List<BaseData> query(DSLink link, POSchema poSchema, String dataQuery, FilterData context) {
        context.setPOSchema(poSchema);
        return repository.queryData(link.getName(),poSchema,context.getSql("-dq-"+dataQuery+"-"),context,context.getSearchCond());
    }

    @Override
    public Page<BaseData> query(DSLink link, POSchema poSchema, String dataQuery, FilterData context, Pageable pageable) {
        context.setPOSchema(poSchema);
        if(pageable!=null)
            context.setPageable(pageable);
        else
            pageable=context.getPageable();
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<BaseData> pages=
                repository.queryData(link.getName(),poSchema,context.getSql("-dq-"+dataQuery+"-"),context,context.getSearchCond(),context.getPages());
        return new PageImpl<BaseData>(pages.getRecords(), pageable, pages.getTotal());

    }

    @Override
    public List<BaseData> selectRaw(DSLink link, POSchema poSchema, String sql, FilterData context) {
        return repository.queryData(link.getName(),poSchema,sql,context,context.getSearchCond());
    }

    @Override
    public boolean execRaw(DSLink link, POSchema poSchema, String sql, BaseData param) {
        repository.executeRaw(link.getName(),poSchema,sql,param);
        return true;
    }

    @Override
    public List<BaseData> fetch(DSLink link, POSchema poSchema, String dataSet, FilterData context) {
        context.setPOSchema(poSchema);
        return repository.queryData(link.getName(),poSchema,context.getSql("-ds-"+dataSet+"-"),context,context.getSearchCond());
    }

    @Override
    public Page<BaseData> fetch(DSLink link, POSchema poSchema, String dataSet, FilterData context, Pageable pageable) {
        context.setPOSchema(poSchema);
        if(pageable!=null)
            context.setPageable(pageable);
        else
            pageable=context.getPageable();
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<BaseData> pages=
                repository.queryData(link.getName(),poSchema,context.getSql("-ds-"+dataSet+"-"),context,context.getSearchCond(),context.getPages());
        return new PageImpl<BaseData>(pages.getRecords(), pageable, pages.getTotal());
    }

    @Override
    public DSLink getDSLink(String datasource) {
        return modelService.getDSLink(datasource);
    }

    @Override
    public POSchema getSchema(DSLink link, String system, String table) {
        return modelService.getPOSchema(system,table,link.getType());
    }
}
