package com.yn.bftl.thirdparty.interceptor;

import com.yn.bftl.common.common.entity.AuditableModel;
import com.yn.bftl.common.common.entity.MetaSequence;
import com.yn.bftl.common.common.entity.Model;
import com.yn.bftl.common.modules.auth.entity.User;
import com.yn.bftl.thirdparty.common.enums.OperatorType;
import com.yn.bftl.thirdparty.common.repository.UserRepository;
import com.yn.bftl.thirdparty.common.util.SpringContextHolder;
import com.yn.bftl.thirdparty.core.db.annotation.Sequence;
import com.yn.bftl.thirdparty.core.db.jpa.JpaSequence;
import org.hibernate.EmptyInterceptor;
import org.hibernate.Transaction;
import org.hibernate.type.Type;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.persistence.PersistenceException;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Optional;

import static com.yn.bftl.thirdparty.aop.SessionAspect.*;

@Component
public class AuditInterceptor extends EmptyInterceptor {

    public static final ThreadLocal<User> CURRENT_USER = new ThreadLocal<>();

    private static final String UPDATED_BY = "updatedBy";
    private static final String UPDATED_ON = "updatedOn";
    private static final String CREATED_BY = "createdBy";
    private static final String CREATED_ON = "createdOn";

    /**
     * 事务开始后
     */
    @Override
    public void afterTransactionBegin(Transaction tx) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        if (OperatorType.MEMBER_USER.equals(request.getAttribute(OPERATOR_TYPE))) {
            CURRENT_USER.set((User) request.getAttribute(OPERATOR_TOKEN));
        }
    }

    /**
     * 事务完成后
     */
    @Override
    public void afterTransactionCompletion(Transaction tx) {
        CURRENT_USER.remove();
    }

    /**
     * 事务完成前
     */
    @Override
    public void beforeTransactionCompletion(Transaction tx) {
    }

    @Override
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
        if (!(entity instanceof AuditableModel)) {
            return false;
        }
        final User user = this.getUser();
        for (int i = 0; i < propertyNames.length; i++) {
            if (!canUpdate(entity, propertyNames[i], previousState[i], currentState[i])) {
                throw new PersistenceException(String.format("You can't update: %s#%s, values (%s=%s)", entity.getClass().getName(), id, propertyNames[i], currentState[i]));
            }
            if (UPDATED_ON.equals(propertyNames[i])) {
                currentState[i] = LocalDateTime.now();
            }
            if (UPDATED_BY.equals(propertyNames[i]) && user != null) {
                currentState[i] = user;
            }
        }
        return true;
    }

    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        boolean changed = updateSequence(entity, propertyNames, state);
        if (!(entity instanceof AuditableModel)) {
            return changed;
        }
        final User user = this.getUser();
        for (int i = 0; i < propertyNames.length; i++) {
            if (state[i] != null) {
                continue;
            }
            if (CREATED_ON.equals(propertyNames[i]) && state[i] == null) {
                state[i] = LocalDateTime.now();
            }
            if (CREATED_BY.equals(propertyNames[i]) && user != null) {
                state[i] = user;
            }
        }
        return true;
    }

    private User getUser() {
        User user = CURRENT_USER.get();
        Long userId = 1L;
        if (user != null) {
            userId = user.getId();
        }
        UserRepository userRepository = SpringContextHolder.getBean(UserRepository.class);
        Optional<User> userOptional = userRepository.findById(userId);
        return userOptional.get();
    }

    @Override
    public void onDelete(
            Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        if (!canDelete(entity)) {
            throw new PersistenceException(String.format("You can't delete: %s#%s", entity.getClass().getName(), id));
        }
    }

    private boolean updateSequence(Object entity, String[] names, Object[] state) {
        if ((entity instanceof MetaSequence) || !(entity instanceof Model)) {
            return false;
        }
        Class<?> entityClass = entity.getClass();
        Field[] declaredFields = entityClass.getDeclaredFields();
        boolean updated = false;
        for (int i = 0; i < declaredFields.length; i++) {
            if (state[i] != null) {
                continue;
            }
            boolean annotationPresent = declaredFields[i].isAnnotationPresent(Sequence.class);
            if (annotationPresent) {
                state[i] = JpaSequence.nextValue(declaredFields[i].getName());
                updated = true;
            }
        }
        return updated;
    }

    private boolean canUpdate(Object entity, String propertyName, Object o, Object o1) {
        return true;
    }

    private boolean canDelete(Object entity) {
        return true;
    }
}
