package cn.ibizlab.pms.util.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import cn.ibizlab.pms.util.annotation.Audit;
import cn.ibizlab.pms.util.annotation.DEField;
import cn.ibizlab.pms.util.domain.EntityBase;
import cn.ibizlab.pms.util.domain.IBZDataAudit;
import cn.ibizlab.pms.util.helper.DEFieldCacheMap;
import cn.ibizlab.pms.util.mapper.IBZDataAuditMapper;
import cn.ibizlab.pms.util.security.AuthenticationUser;
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.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 实体[DataAudit] 服务对象接口实现
 */
@Service
@Slf4j
public class SimpleAuditService extends ServiceImpl<IBZDataAuditMapper, IBZDataAudit> implements IBZDataAuditService {

    private final ExpressionParser parser = new SpelExpressionParser();

    private static List cacheMap = Collections.synchronizedList(new ArrayList());

    /**
     * 定时保存审计记录
     */
    @Scheduled(fixedRate = 10000)
    public void saveAudit() {
        if(cacheMap.size()>0){
            log.info(String.format("正在保存审计数据，当前审计集合数量为[%s]",cacheMap.size()));
            List temp=new ArrayList();
            if(cacheMap.size()<500)
              temp.addAll(cacheMap);
            else
              temp.addAll(cacheMap.subList(0,500));
            this.saveBatch(temp);
            cacheMap.removeAll(temp);
            log.info(String.format("保存完成，当前审计集合数量为[%s]",cacheMap.size()));
        }
    }

    /**
     * 新建审计日志
     * @param request
     * @param entity
     * @param idValue
     * @param auditFields
     */
    @Override
    public void createAudit(HttpServletRequest request, EntityBase entity, Object idValue, Map<String, Audit> auditFields) {
        IBZDataAudit dataAudit =new IBZDataAudit();
        dataAudit.setOppersonid(AuthenticationUser.getAuthenticationUser().getUserid());
        dataAudit.setOppersonname(String.format("%s[%s]",AuthenticationUser.getAuthenticationUser().getPersonname(),AuthenticationUser.getAuthenticationUser().getOrgname()));
        dataAudit.setAudittype("CREATE");
        dataAudit.setAuditobject(entity.getClass().getSimpleName());
        dataAudit.setAuditobjectdata(idValue);
        dataAudit.setOptime(new Timestamp(new Date().getTime()));
        if(request!=null)
            dataAudit.setIpaddress(getIpAddress(request, AuthenticationUser.getAuthenticationUser()));
        dataAudit.setAuditinfo(getAuditInfo(entity,auditFields));
        dataAudit.setIsdatachanged(1);
        cacheMap.add(dataAudit);
    }

    /**
     * 更新审计日志
     * @param request
     * @param beforeEntity
     * @param serviceObj
     * @param idValue
     * @param auditFields
     */
    @SneakyThrows
    public void updateAudit(HttpServletRequest request, EntityBase beforeEntity, Object serviceObj, Object idValue, Map<String, Audit> auditFields){
        //获取更新后的实体
        EntityBase afterEntity=getEntity(serviceObj,idValue);
        //获取更新后的审计内容
        String auditInfo=getUpdateAuditInfo(beforeEntity,afterEntity,auditFields);//比较更新前后差异内容
        int isDataChanged=1;
        if(StringUtils.isEmpty(auditInfo))//审计内容是否发生变化
            isDataChanged=0;

        IBZDataAudit dataAudit =new IBZDataAudit();
        dataAudit.setOppersonid(AuthenticationUser.getAuthenticationUser().getUserid());
        dataAudit.setOppersonname(String.format("%s[%s]",AuthenticationUser.getAuthenticationUser().getPersonname(),AuthenticationUser.getAuthenticationUser().getOrgname()));
        dataAudit.setAudittype("UPDATE");
        dataAudit.setAuditobject(afterEntity.getClass().getSimpleName());
        dataAudit.setAuditobjectdata(idValue);
        dataAudit.setOptime(new Timestamp(new Date().getTime()));
        if(request!=null)
            dataAudit.setIpaddress(getIpAddress(request, AuthenticationUser.getAuthenticationUser()));
        dataAudit.setAuditinfo(auditInfo);
        dataAudit.setIsdatachanged(isDataChanged);
        cacheMap.add(dataAudit);
    }

    /**
     * 删除审计日志
     * @param request
     * @param entity
     * @param idValue
     * @param auditFields
     */
    public void removeAudit(HttpServletRequest request,EntityBase entity,Object idValue,Map<String, Audit> auditFields){
        IBZDataAudit dataAudit =new IBZDataAudit();
        dataAudit.setOppersonid(AuthenticationUser.getAuthenticationUser().getUserid());
        dataAudit.setOppersonname(String.format("%s[%s]",AuthenticationUser.getAuthenticationUser().getPersonname(),AuthenticationUser.getAuthenticationUser().getOrgname()));
        dataAudit.setAudittype("REMOVE");
        dataAudit.setAuditobject(entity.getClass().getSimpleName());
        dataAudit.setAuditobjectdata(idValue);
        dataAudit.setOptime(new Timestamp(new Date().getTime()));
        if(request!=null)
            dataAudit.setIpaddress(getIpAddress(request, AuthenticationUser.getAuthenticationUser()));
        dataAudit.setAuditinfo(getAuditInfo(entity,auditFields));
        dataAudit.setIsdatachanged(1);
        cacheMap.add(dataAudit);
    }

    private String getAuditInfo(EntityBase entity, Map<String, Audit> auditFields){
        String auditResult="";
        if(auditFields.size()==0)
            return auditResult;

        Map<String, DEField> deFields= DEFieldCacheMap.getDEFields(entity.getClass());
        if(deFields.size()==0)
            return auditResult;

        JSONArray auditFieldArray=new JSONArray();
        for (Map.Entry<String, Audit> auditField : auditFields.entrySet()) {
            Object objFieldName=auditField.getKey();
            String fieldName=String.valueOf(objFieldName);
            DEField deField=null;
            if(deFields.containsKey(fieldName)){
                deField= deFields.get(fieldName);
            }
            if(ObjectUtils.isEmpty(deField))
                continue;
            Object value=dataTransfer(entity.get(fieldName),deField.fieldType(),deField.format());
            if(!StringUtils.isEmpty(value)){
                JSONObject auditFieldObj=new JSONObject();
                auditFieldObj.put("field",deField.value());
                auditFieldObj.put("value",value);
                if(!StringUtils.isEmpty(deField.dict())){
                    auditFieldObj.put("dict",deField.dict());
                }
                auditFieldArray.add(auditFieldObj);
            }
        }
        if(auditFieldArray.size()>0){
            auditResult=auditFieldArray.toString();
        }
        return auditResult;
    }

    /**
     * 获取更新审计内容
     * @param oldData
     * @param newData
     * @param auditFields
     * @return
     */
    private String getUpdateAuditInfo(EntityBase oldData, EntityBase newData, Map<String, Audit> auditFields){
        String auditResult="";
        JSONArray auditFieldArray=new JSONArray();
        if(auditFields.size()==0)
            return auditResult;

        Map<String, DEField> deFields= DEFieldCacheMap.getDEFields(oldData.getClass());
        if(deFields.size()==0)
            return auditResult;

        for (Map.Entry<String, Audit> auditField : auditFields.entrySet()) {
            Object objFieldName=auditField.getKey();//获取注解字段
            String fieldName=String.valueOf(objFieldName); //属性名称
            DEField deField=null;
            if(deFields.containsKey(fieldName)){
                deField= deFields.get(fieldName);
            }
            if(ObjectUtils.isEmpty(deField))
                continue;
            Object oldValue=oldData.get(fieldName);//老属性值
            Object newValue=newData.get(fieldName);//新属性值
            if(!compare(oldValue,newValue)){
                oldValue=dataTransfer(oldValue,deField.fieldType(),deField.format());//属性值转换
                newValue=dataTransfer(newValue,deField.fieldType(),deField.format());//属性值转换
                JSONObject auditFieldObj=new JSONObject();
                auditFieldObj.put("field",deField.value());
                auditFieldObj.put("beforevalue",oldValue);
                auditFieldObj.put("value",newValue);
                if(!StringUtils.isEmpty(deField.dict())){
                    auditFieldObj.put("dict",deField.dict());
                }
                auditFieldArray.add(auditFieldObj);
            }
        }
        if(auditFieldArray.size()>0){
            auditResult=auditFieldArray.toString();
        }
        return auditResult;
    }

    /**
     * 数据转换
     * @param value 转换值
     * @param dataType 转换字段类型
     * @param strFormat   转换字段格式化文本
     * @return
     */
    private String dataTransfer(Object value,String dataType, String strFormat){
        if(value==null)
            return "";
        String transResult=value.toString();
        if((dataType.equals("DATE") || dataType.equals("DATETIME") || dataType.equals("TIME")) && (!StringUtils.isEmpty(strFormat))){  //时间类型转换
            Timestamp timestamp =(Timestamp)value;
            Date date =timestamp;
            SimpleDateFormat format =new SimpleDateFormat(strFormat);
            transResult=format.format(date);
        }
        return transResult;
    }

    /**
     * 对象比较
     * @param sourceObj 比较源对象
     * @param targetObj 比较目标对象
     * @return
     */
    private boolean compare(Object sourceObj,Object targetObj){
        if(sourceObj==null && targetObj==null)
            return true;
        if(sourceObj==null && targetObj!=null)
            return false;
        return sourceObj.equals(targetObj);
    }

    /**
     * 获取实体
     * @param service
     * @param id
     * @return
     */
    @SneakyThrows
    private EntityBase getEntity(Object service, Object id){
        EntityBase entity=null;
        if(!ObjectUtils.isEmpty(service)){
            EvaluationContext oldContext = new StandardEvaluationContext();
            oldContext.setVariable("service",service);
            oldContext.setVariable("id",id);
            Expression oldExp = parser.parseExpression("#service.get(#id)");
            return oldExp.getValue(oldContext, EntityBase.class);
        }
        return entity;
    }

    /**
     * 获取Ip地址
     * @param request
     * @return
     */
    public String getIpAddress(HttpServletRequest request, AuthenticationUser authenticationUser) {
        //客户端有提交ip，以提交的ip为准
        if(authenticationUser != null && !StringUtils.isEmpty(authenticationUser.getAddr())){
            return authenticationUser.getAddr();
        }
        if(request == null)
            return "";
        String Xip = request.getHeader("X-Real-IP");
        String XFor = request.getHeader("X-Forwarded-For");
        if(!StringUtils.isEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)){
            //多次反向代理后会有多个ip值，第一个ip才是真实ip
            int index = XFor.indexOf(",");
            if(index != -1){
                return XFor.substring(0,index);
            }else{
                return XFor;
            }
        }
        XFor = Xip;
        if(!StringUtils.isEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)){
            return XFor;
        }
        if (StringUtils.isEmpty(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isEmpty(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isEmpty(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getRemoteAddr();
        }
        return XFor;
    }
}