package cn.ibizlab.util.aspect;

import lombok.SneakyThrows;
import cn.ibizlab.util.annotation.VersionCheck;
import cn.ibizlab.util.domain.EntityBase;
import cn.ibizlab.util.errors.BadRequestAlertException;
import cn.ibizlab.util.helper.RuleUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * 数据库版本检查
 */
@Aspect
@Order(50)
@Component
public class VersionCheckAspect
{
    private final ExpressionParser parser = new SpelExpressionParser();
    private final String IgnoreField="ignoreversioncheck";

    @SneakyThrows
    @Before("execution(* cn.ibizlab.*.rest.*.update(..)) &&  @annotation(versionCheck)")
    public void BeforeUpdate(JoinPoint point, VersionCheck versionCheck){
        Object[] args = point.getArgs();
        Object id=args[0];
        Object dto=args[1];
        if(ObjectUtils.isEmpty(id) || ObjectUtils.isEmpty(dto))
            return;
        String versionField=versionCheck.versionfield();
        if(StringUtils.isEmpty(versionField))
            return;
        versionCheck(versionCheck,point.getTarget(),dto,id);
    }

    @SneakyThrows
    @Before("execution(* cn.ibizlab.*.rest.*.updateBy*(..)) &&  @annotation(versionCheck)")
    public void BeforeUpdateBy(JoinPoint point, VersionCheck versionCheck){
        Object[] args = point.getArgs();
        if(args.length>=2){
            Object id=args[args.length-2];
            Object dto=args[args.length-1];
            if(ObjectUtils.isEmpty(id) || ObjectUtils.isEmpty(dto))
                return;
            String versionField=versionCheck.versionfield();
            if(StringUtils.isEmpty(versionField))
                return;
            versionCheck(versionCheck,point.getTarget(),dto,id);
        }
    }

    private void versionCheck(VersionCheck versionCheck,Object resource,Object dto,Object id ){
        EvaluationContext context = new StandardEvaluationContext();
        context.setVariable("dto",dto);
        //忽略版本检查
        Expression dtoParamsExp = parser.parseExpression("#dto.extensionparams");
        Map dtoParam=dtoParamsExp.getValue(context, Map.class);
        if(!ObjectUtils.isEmpty(dtoParam) && !ObjectUtils.isEmpty(dtoParam.get(IgnoreField)) && dtoParam.get(IgnoreField).equals(1))
            return;
        Expression newExp = parser.parseExpression(String.format("#dto.%s",versionCheck.versionfield()));
        Object newVersion=newExp.getValue(context);
        if(ObjectUtils.isEmpty(newVersion))
            return;
        //进行版本检查
        Object oldVersion =getDBVersion(versionCheck,getService(resource,versionCheck.entity()),id);
        if(!ObjectUtils.isEmpty(oldVersion)){
            if(RuleUtils.gt(newVersion,oldVersion))
                throw new BadRequestAlertException("数据已变更,可能后台数据已被修改,请重新加载数据","VersionCheckAspect","versionCheck");
        }
    }

    /**
     * 获取实体服务对象
     * @param resource
     * @param entity
     * @return
     */
    @SneakyThrows
    private Object getService(Object resource,String entity){
        Object service = null;
        Field[] fields= resource.getClass().getDeclaredFields();
        for(Field field : fields){
            if(field.getModifiers()==1 && field.getName().equalsIgnoreCase(String.format("%sService",entity))){
                service=field.get(resource);
                break;
            }
        }
        return service;
    }

    /**
     * 获取数据库版本
     * @param versionCheck
     * @param service
     * @param id
     * @return
     */
    @SneakyThrows
    private Object getDBVersion(VersionCheck versionCheck,Object service,Object id){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Timestamp dbVersion=null;
        String versionField=versionCheck.versionfield();
        if(!ObjectUtils.isEmpty(service)){
            EvaluationContext oldContext = new StandardEvaluationContext();
            oldContext.setVariable("service",service);
            oldContext.setVariable("id",id);
            Expression oldExp = parser.parseExpression("#service.get(#id)");
            EntityBase oldEntity =oldExp.getValue(oldContext, EntityBase.class);
            Object oldDate=oldEntity.get(versionField);
            if(oldDate!=null && oldDate instanceof Timestamp){
                Timestamp db_time= (Timestamp) oldDate;
                Date db_date = sdf.parse(sdf.format(db_time));
                dbVersion=new Timestamp(db_date.getTime());
            }
        }
        return dbVersion;
    }
}