package com.bocloud.ims.repository.resource.hmc;

import com.bocloud.common.model.Pager;
import com.bocloud.common.model.Param;
import com.bocloud.common.model.Sign;
import com.bocloud.database.core.BasicDao;
import com.bocloud.database.utils.QueryBuilder;
import com.bocloud.entity.annotations.Table;
import com.bocloud.ims.entity.resource.hmc.HmdGenericEntity;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

public abstract class HmdGenericDao<T extends HmdGenericEntity> extends BasicDao {

    private final Class<T> tClass;
    private final String table;

    @SuppressWarnings("unchecked")
    public HmdGenericDao(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate npJdbcTemplate) {
        Class targetClass = this.getClass();
        while (!targetClass.getSuperclass().equals(HmdGenericDao.class)) {
            targetClass = targetClass.getSuperclass();
        }
        Type genericSuperclass = targetClass.getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        this.tClass = (Class) actualTypeArguments[0];
        Table tableAnno = this.tClass.getDeclaredAnnotation(Table.class);
        this.table = Optional.ofNullable(tableAnno).map(Table::value).orElseThrow(IllegalArgumentException::new);
    }

    public abstract void extra(T t);

    protected String getListSql() {
        return "select a.* from " +table + " a where 1=1 ";
    }

    protected String getCountSql() {
        return "select count(1) from " +table + " a where 1=1 ";
    }

    public List<T> search(Map<String, Object> map) {
        map = new HashMap<>(map);
        map.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
        Param param = new Param(map, Sign.EQ);
        List<Param> params = Collections.singletonList(param);
        return this.list(1, Integer.MAX_VALUE, params, new HashMap<>());
    }

    @SuppressWarnings("unchecked")
    public List<T> list(int page, int rows, List<Param> params, Map<String, String> sorter) {
        String sql = QueryBuilder.buildRaw(this.getListSql(), new Pager(page, rows, params, sorter), "");
        Map<String, Object> paramMap = QueryBuilder.getParam(params);
        return this.queryForList(sql, paramMap, tClass).stream().map(o -> (T) o).collect(Collectors.toList());
    }

    public void copy(T srcT, T desT, String... ignores) {
        String[] finalIgnores = ArrayUtils.addAll(ignores, "id", "expending");
        BeanUtils.copyProperties(srcT, desT, finalIgnores);
    }

    public int count(List<Param> params) {
        String sql = QueryBuilder.buildRaw(this.getCountSql(), params, null, "");
        Map<String, Object> paramMap = QueryBuilder.getParam(params);
        return this.countQuery(sql, paramMap).intValue();
    }

    public void insert(T t, long userId) {
        super.saveEntity(t);
    }

    public void update(T t, long userId) {
        super.updateEntity(t);
    }

    public void delete(long id) {
        String sql = "delete from " + table + " where id = :id ";
        Map<String, Object> params = Collections.singletonMap("id", id);
        super.update(sql, params);
    }

    public void clear(long id) {
        this.delete(id);
    }


    @SuppressWarnings("unchecked")
    public Optional<T> get(long id) {
        String sql = "select * from " + table + " where is_deleted = 0 and id = :id";
        Map<String, Object> params = Collections.singletonMap("id", id);
        List<T> oldTs = super.queryForList(sql, params, tClass).stream().map(o -> (T) o).collect(Collectors.toList());
        return oldTs.stream().findFirst();
    }

    public void save(T t, long userId) {
        if (t.getId() == null) {
            this.insert(t, userId);
        } else {
            this.update(t, userId);
        }
    }

    public void startPending(long id) {
        String sql = "update " + table + " set pending = 1 where id = :id ";
        Map<String, Object> params = Collections.singletonMap("id", id);
        super.update(sql, params);
    }

    public void stopPending(long id) {
        String sql = "update " + table + " set pending = 0 where id = :id ";
        Map<String, Object> params = Collections.singletonMap("id", id);
        super.update(sql, params);
    }

    public void startAllPending(long id) {
        this.startPending(id);
    }

    public void stopAllPending(long id) {
        this.stopPending(id);
    }

    protected void convertInstance(final T srcT, final T desT) {
        this.copy(srcT, desT);
    }

    public void syncInstance(final T newT, final T oldT, long userId) {
        this.convertInstance(newT, oldT);
        this.save(oldT, userId);
        newT.setId(oldT.getId());
    }

    @Transactional
    public void syncList(final List<T> newTs, final List<T> oldTs, long userId) {
        newTs.forEach(newT -> {
            T oldT = oldTs.stream().filter(newT::equals).findFirst().orElseGet(this::newT);
            this.syncInstance(newT, oldT, userId);
        });
        oldTs.stream().filter(oldT -> !newTs.contains(oldT)).map(T::getId).forEach(this::clear);
    }

    private T newT() {
        try {
            return tClass.getConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new IllegalStateException(e);
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Class[" + tClass.getName() + "] requires a constructor without any parameters");
        }
    }

}
