package me.chyxion.tigon.webmvc.exception;

import java.util.Map;
import java.util.HashMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.Order;
import me.chyxion.tigon.exception.BaseException;
import me.chyxion.tigon.webmvc.JSONViewDataModel;
import me.chyxion.tigon.exception.InvalidParamException;
import me.chyxion.tigon.exception.InvalidStateException;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.MissingServletRequestParameterException;

/**
 * @version 0.0.1
 * @since 0.0.3
 * @author Shaun Chyxion <br>
 * chyxion@163.com <br>
 * Feb 3, 2015 2:06:58 PM
 */
@Order(64)
@Slf4j
public class DefaultExceptionResolver implements ExceptionResolver {
    @Autowired(required = false)
    private ExceptionMessage exceptionMessage;

    private static final Map<Class<? extends Throwable>, Integer> EXCEPTIONS_MAP = 
            new HashMap<Class<? extends Throwable>, Integer>() {
        private static final long serialVersionUID = 1L;
        {
            // Database Exceptions
//            put(CannotAcquireLockException.class, 5030);
//            put(CannotSerializeTransactionException.class, 5031);
//            put(CleanupFailureDataAccessException.class, 5032);
//            put(ConcurrencyFailureException.class, 5033);
//            put(DataAccessException.class, 5034);
//            put(DataAccessResourceFailureException.class, 5035);
//            put(DataIntegrityViolationException.class, 5036);
//            put(DataRetrievalFailureException.class, 5037);
//            put(DeadlockLoserDataAccessException.class, 5038);
//            put(DuplicateKeyException.class, 5039);
//            put(EmptyResultDataAccessException.class, 5040);
//            put(IncorrectResultSizeDataAccessException.class, 5041);
//            put(IncorrectUpdateSemanticsDataAccessException.class, 5042);
//            put(InvalidDataAccessApiUsageException.class, 5043);
//            put(InvalidDataAccessResourceUsageException.class, 5044);
//            put(NonTransientDataAccessException.class, 5045);
//            put(NonTransientDataAccessResourceException.class, 5046);
//            put(OptimisticLockingFailureException.class, 5047);
//            put(PermissionDeniedDataAccessException.class, 5048);
//            put(PessimisticLockingFailureException.class, 5049);
//            put(QueryTimeoutException.class, 5050);
//            put(RecoverableDataAccessException.class, 5051);
//            put(TransientDataAccessException.class, 5052);
//            put(TransientDataAccessResourceException.class, 5053);
//            put(TypeMismatchDataAccessException.class, 5054);
//            put(UncategorizedDataAccessException.class, 5055);
            // 4002
            put(IllegalStateException.class, InvalidStateException.CODE);
            put(ConversionFailedException.class, InvalidStateException.CODE);
            put(IllegalArgumentException.class, InvalidStateException.CODE);
            // 4001
            put(InvalidParamException.class, InvalidParamException.CODE);
            put(MissingServletRequestParameterException.class, InvalidParamException.CODE);
            put(ServletRequestBindingException.class, InvalidParamException.CODE);
        }
    };

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean accept(Throwable ex) {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public JSONViewDataModel process(JSONViewDataModel model) {
        Throwable ex = model.getException();
        log.error("Exception [{}] [{}] Caused.", ex.getClass(), ex.getMessage(), ex);
        Object code = model.getCode();
        if (code == null || 
            (code instanceof Number && ((Number) code).intValue() == 0) ||
            (code instanceof String && StringUtils.isBlank((String) code))) {
            code = getErrorCode(ex);
            if (code == null) {
                code = BaseException.CODE;
            }
            model.setCode(code);
        }
        if (model.getMessage() == null) {
            Object message = null;
            if (exceptionMessage != null) {
                message = exceptionMessage.get(code);
                log.info("Get Custom Error Message [{}] By Code [{}].", message, code);
            }
            if (message == null) {
                message = getErrorMessage(ex, code);
            }
            model.setMessage(message);
        }
        return model;
    }

    /**
     * @param ex exception
     * @return error code
     */
    protected Object getErrorCode(Throwable ex) {
        return EXCEPTIONS_MAP.get(ex.getClass());
    }

    /**
     * @param ex exception
     * @param code error code
     * @return error message
     */
    protected Object getErrorMessage(Throwable ex, Object code) {
        Object message = null;
        for (Throwable e : ExceptionUtils.getThrowableList(ex)) {
            String msg = e.getMessage();
            if (StringUtils.isNotBlank(msg)) {
                message = msg;
                break;
            }
        }
        log.info("No Custom Error Message Found, Get Message [{}] From Exception.", message);
        return message;
    }
}
