/*
 * Copyright 2014-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.rockagen.gnext.service.spring.security.extension;

import com.rockagen.commons.util.CommUtil;
import com.rockagen.gnext.enums.UserReferer;
import com.rockagen.gnext.po.AuthUser;
import com.rockagen.gnext.service.AuthUserServ;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.Optional;

/**
 * Authentication Handler
 *
 * @author ra
 * @since JDK1.8
 */
public class ExAuthenticationHandler implements MessageSourceAware {


    protected MessageSourceAccessor messages=SpringSecurityMessageSource.getAccessor();
    /**
     * HttpServletRequest Header params
     */
    private String tokenName = "X-AUTH-TOKEN";

    private String userReferer = "X-Referer";

    private String username = "uid";

    private int maxFailedAttempts = 6;
    // 3 hours
    private long lockedTime = 10800000;

    private AuthUserServ authUserServ;

    private ExTokenAuthentication exTokenAuthentication;


    /**
     * Authentication success handler
     *
     * @param request        request
     * @param response       response
     * @param authentication {@link org.springframework.security.core.Authentication}
     */
    public void successHandler(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

        String uid = authentication.getName();
        successRegister(uid, request);
        // Response Token
        String token = exTokenAuthentication.newToken(uid);
        if (CommUtil.isNotBlank(token)) {
            response.setHeader(tokenName, token);
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        }
    }

    /**
     * Success register
     *
     * @param uid     uid
     * @param request request
     */
    private void successRegister(String uid, HttpServletRequest request) {
        String currentIp = new ExWebAuthenticationDetails(request).getRemoteAddress();
        Optional<AuthUser> user = authUserServ.load(uid);
        user.ifPresent(x -> {
                    Date now = new Date();
                    x.setLastSignInAt(x.getCurrentSignInAt());
                    x.setCurrentSignInAt(now);
                    x.setLastSignInIp(x.getCurrentSignInIp());
                    x.setCurrentSignInIp(currentIp);
                    Integer signCount = Optional.ofNullable(x.getSignInCount()).orElse(0);
                    x.setSignInCount(++signCount);
                    x.setFailedAttempts(0);
                    // Referer
                    String referer = request.getHeader(userReferer);
                    x.setLastUserReferer(getReferer(referer));
                    authUserServ.add(x);
                }
        );
    }


    /**
     * Authentication failure handler
     *
     * @param request  request
     * @param response response
     */
    public void failureHandler(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String uid = request.getParameter(username);
        try {
            failureRegister(uid, request);
        } catch (AuthenticationException e) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
        }
    }

    /**
     * Failure register
     *
     * @param uid     uid
     * @param request request
     */
    private void failureRegister(String uid, HttpServletRequest request) {
        String currentIp = new ExWebAuthenticationDetails(request).getRemoteAddress();
        Optional<AuthUser> u = authUserServ.load(uid);

        if (u.filter(AuthUser::enabled).isPresent()) {
            AuthUser user = u.get();
            Date date = new Date();

            long now = date.getTime();


            // Failed attempts count ++
            Integer failedAttempts = user.getFailedAttempts();
            if (failedAttempts == null) {
                failedAttempts = 0;
            }
            user.setFailedAttempts(++failedAttempts);
            user.setLastSignInAt(user.getCurrentSignInAt());
            user.setCurrentSignInAt(date);
            user.setLastSignInIp(user.getCurrentSignInIp());
            user.setCurrentSignInIp(currentIp);
            Integer signCount = user.getSignInCount();
            if (signCount == null) {
                signCount = 0;
            }
            user.setSignInCount(++signCount);
            // Referer
            String referer = request.getHeader(userReferer);
            user.setLastUserReferer(getReferer(referer));

            if(user.getLastSignInAt()!=null){
                long lastTime = user.getLastSignInAt().getTime();
                // auto lock
                if (user.getFailedAttempts() >= maxFailedAttempts && (now - lastTime) <= lockedTime) {
                    // Locked user
                    user.setLockedAt(date);
                    user.setEnabled(0);
                }
            }

            authUserServ.add(user);

            // locked?
            if (user.getEnabled() < 1) {
                throw new DisabledException(messages.getMessage("AccountStatusUserDetailsChecker.locked"));
            }

            int onlyCount = maxFailedAttempts - user.getFailedAttempts();
            throw new BadCredentialsException(messages.getMessage(
                    "AccountStatusUserDetailsChecker.onlyCount", new Object[]{onlyCount}));


        } else {
            throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }

    }

    public UserReferer getReferer(String referer) {
        UserReferer state = UserReferer.UNKNOWN;
        try {
            state = UserReferer.valueOf(referer);
        } catch (Exception e) {
            // DO NOT
        }
        return state;
    }


    public void setUserReferer(String userReferer) {
        this.userReferer = userReferer;
    }

    public void setAuthUserServ(AuthUserServ authUserServ) {
        this.authUserServ = authUserServ;
    }


    public void setTokenName(String tokenName) {
        this.tokenName = tokenName;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setMaxFailedAttempts(int maxFailedAttempts) {
        this.maxFailedAttempts = maxFailedAttempts;
    }

    public void setLockedTime(long lockedTime) {
        this.lockedTime = lockedTime;
    }

    public void setExTokenAuthentication(ExTokenAuthentication exTokenAuthentication) {
        this.exTokenAuthentication = exTokenAuthentication;
    }

    @Override
    public void setMessageSource(MessageSource messageSource) {
        messages=new MessageSourceAccessor(messageSource);
    }
}
