package com.mossle.security.util;

import java.net.InetAddress;

import java.util.Date;

import javax.annotation.Resource;

import com.mossle.api.audit.AuditConnector;
import com.mossle.api.audit.AuditDTO;

import com.mossle.core.auth.LoginEvent;

import com.mossle.security.impl.SpringSecurityUserAuth;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
import org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent;
import org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent;
import org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent;
import org.springframework.security.authentication.event.AuthenticationFailureLockedEvent;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.WebAuthenticationDetails;

public class SpringSecurityListener implements ApplicationListener,
        ApplicationContextAware {
    private static Logger logger = LoggerFactory
            .getLogger(SpringSecurityListener.class);
    private AuditConnector auditConnector;
    private ApplicationContext ctx;

    public void onApplicationEvent(ApplicationEvent event) {
        try {
            if (event instanceof InteractiveAuthenticationSuccessEvent) {
                this.logLoginSuccess(event);
            }

            if (event instanceof AuthenticationFailureBadCredentialsEvent) {
                this.logBadCredential(event);
            }

            if (event instanceof AuthenticationFailureLockedEvent) {
                this.logLocked(event);
            }

            if (event instanceof AuthenticationFailureDisabledEvent) {
                this.logDisabled(event);
            }

            if (event instanceof AuthenticationFailureExpiredEvent) {
                this.logAccountExpired(event);
            }

            if (event instanceof AuthenticationFailureCredentialsExpiredEvent) {
                this.logCredentialExpired(event);
            }
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
        }
    }

    public void logLoginSuccess(ApplicationEvent event) throws Exception {
        InteractiveAuthenticationSuccessEvent interactiveAuthenticationSuccessEvent = (InteractiveAuthenticationSuccessEvent) event;
        Authentication authentication = interactiveAuthenticationSuccessEvent
                .getAuthentication();

        String tenantId = this.getTenantId(authentication);
        Object principal = authentication.getPrincipal();
        String userId = null;

        if (principal instanceof SpringSecurityUserAuth) {
            userId = ((SpringSecurityUserAuth) principal).getId();
        } else {
            userId = authentication.getName();
        }

        AuditDTO auditDto = new AuditDTO();
        auditDto.setUserId(userId);
        auditDto.setAuditTime(new Date());
        auditDto.setAction("login");
        auditDto.setResult("success");
        auditDto.setApplication("lemon");
        auditDto.setClient(getUserIp(authentication));
        auditDto.setServer(InetAddress.getLocalHost().getHostAddress());
        auditDto.setTenantId(tenantId);
        auditConnector.log(auditDto);

        // 登录成功，再发送一个消息，以后这里的功能都要改成listener，不用直接写接口了。解耦更好一些。
        ctx.publishEvent(new LoginEvent(authentication, userId, this
                .getSessionId(authentication), "success", "default", tenantId));
    }

    public void logBadCredential(ApplicationEvent event) throws Exception {
        AuthenticationFailureBadCredentialsEvent authenticationFailureBadCredentialsEvent = (AuthenticationFailureBadCredentialsEvent) event;
        Authentication authentication = authenticationFailureBadCredentialsEvent
                .getAuthentication();
        logger.info("logBadCredential : {}", authentication);

        String tenantId = this.getTenantId(authentication);
        Object principal = authentication.getPrincipal();
        String userId = null;

        if (principal instanceof SpringSecurityUserAuth) {
            userId = ((SpringSecurityUserAuth) principal).getId();
        } else {
            userId = authentication.getName();
        }

        AuditDTO auditDto = new AuditDTO();
        auditDto.setUserId(userId);
        auditDto.setAuditTime(new Date());
        auditDto.setAction("login");
        auditDto.setResult("failure");
        auditDto.setApplication("lemon");
        auditDto.setClient(getUserIp(authentication));
        auditDto.setServer(InetAddress.getLocalHost().getHostAddress());
        auditDto.setDescription(authenticationFailureBadCredentialsEvent
                .getException().getMessage());
        auditDto.setTenantId(tenantId);
        auditConnector.log(auditDto);

        ctx.publishEvent(new LoginEvent(authentication, userId, this
                .getSessionId(authentication), "badCredentials", "default",
                tenantId));
    }

    public void logLocked(ApplicationEvent event) throws Exception {
        AuthenticationFailureLockedEvent authenticationFailureLockedEvent = (AuthenticationFailureLockedEvent) event;
        Authentication authentication = authenticationFailureLockedEvent
                .getAuthentication();
        logger.info("logLocked : {}", authentication);

        String tenantId = this.getTenantId(authentication);

        Object principal = authentication.getPrincipal();
        String userId = null;

        if (principal instanceof SpringSecurityUserAuth) {
            userId = ((SpringSecurityUserAuth) principal).getId();
        } else {
            userId = authentication.getName();
        }

        AuditDTO auditDto = new AuditDTO();
        auditDto.setUserId(userId);
        auditDto.setAuditTime(new Date());
        auditDto.setAction("login");
        auditDto.setResult("failure");
        auditDto.setApplication("lemon");
        auditDto.setClient(getUserIp(authentication));
        auditDto.setServer(InetAddress.getLocalHost().getHostAddress());
        auditDto.setDescription(authenticationFailureLockedEvent.getException()
                .getMessage());
        auditDto.setTenantId(tenantId);
        auditConnector.log(auditDto);

        ctx.publishEvent(new LoginEvent(authentication, userId, this
                .getSessionId(authentication), "locked", "default", tenantId));
    }

    public void logDisabled(ApplicationEvent event) throws Exception {
        AuthenticationFailureDisabledEvent authenticationFailureDisabledEvent = (AuthenticationFailureDisabledEvent) event;
        Authentication authentication = authenticationFailureDisabledEvent
                .getAuthentication();
        logger.info("logDisabled : {}", authentication);

        String tenantId = this.getTenantId(authentication);

        Object principal = authentication.getPrincipal();
        String userId = null;

        if (principal instanceof SpringSecurityUserAuth) {
            userId = ((SpringSecurityUserAuth) principal).getId();
        } else {
            userId = authentication.getName();
        }

        AuditDTO auditDto = new AuditDTO();
        auditDto.setUserId(userId);
        auditDto.setAuditTime(new Date());
        auditDto.setAction("login");
        auditDto.setResult("failure");
        auditDto.setApplication("lemon");
        auditDto.setClient(getUserIp(authentication));
        auditDto.setServer(InetAddress.getLocalHost().getHostAddress());
        auditDto.setDescription(authenticationFailureDisabledEvent
                .getException().getMessage());
        auditDto.setTenantId(tenantId);
        auditConnector.log(auditDto);

        ctx.publishEvent(new LoginEvent(authentication, userId, this
                .getSessionId(authentication), "disabled", "default", tenantId));
    }

    public void logCredentialExpired(ApplicationEvent event) throws Exception {
        AuthenticationFailureCredentialsExpiredEvent authenticationFailureCredentialsExpiredEvent = (AuthenticationFailureCredentialsExpiredEvent) event;
        Authentication authentication = authenticationFailureCredentialsExpiredEvent
                .getAuthentication();
        logger.info("logCredentialExpired : {}", authentication);

        String tenantId = this.getTenantId(authentication);

        Object principal = authentication.getPrincipal();
        String userId = null;

        if (principal instanceof SpringSecurityUserAuth) {
            userId = ((SpringSecurityUserAuth) principal).getId();
        } else {
            userId = authentication.getName();
        }

        AuditDTO auditDto = new AuditDTO();
        auditDto.setUserId(userId);
        auditDto.setAuditTime(new Date());
        auditDto.setAction("login");
        auditDto.setResult("failure");
        auditDto.setApplication("lemon");
        auditDto.setClient(getUserIp(authentication));
        auditDto.setServer(InetAddress.getLocalHost().getHostAddress());
        auditDto.setDescription(authenticationFailureCredentialsExpiredEvent
                .getException().getMessage());
        auditDto.setTenantId(tenantId);
        auditConnector.log(auditDto);

        ctx.publishEvent(new LoginEvent(authentication, userId, this
                .getSessionId(authentication), "credentialExpired", "default",
                tenantId));
    }

    public void logAccountExpired(ApplicationEvent event) throws Exception {
        AuthenticationFailureExpiredEvent authenticationFailureExpiredEvent = (AuthenticationFailureExpiredEvent) event;
        Authentication authentication = authenticationFailureExpiredEvent
                .getAuthentication();
        logger.info("logAccountExpired : {}", authentication);

        String tenantId = this.getTenantId(authentication);

        Object principal = authentication.getPrincipal();
        String userId = null;

        if (principal instanceof SpringSecurityUserAuth) {
            userId = ((SpringSecurityUserAuth) principal).getId();
        } else {
            userId = authentication.getName();
        }

        AuditDTO auditDto = new AuditDTO();
        auditDto.setUserId(userId);
        auditDto.setAuditTime(new Date());
        auditDto.setAction("login");
        auditDto.setResult("failure");
        auditDto.setApplication("lemon");
        auditDto.setClient(getUserIp(authentication));
        auditDto.setServer(InetAddress.getLocalHost().getHostAddress());
        auditDto.setDescription(authenticationFailureExpiredEvent
                .getException().getMessage());
        auditDto.setTenantId(tenantId);
        auditConnector.log(auditDto);

        ctx.publishEvent(new LoginEvent(authentication, userId, this
                .getSessionId(authentication), "accountExpired", "default",
                tenantId));
    }

    // ~
    public String getUserIp(Authentication authentication) {
        if (authentication == null) {
            return "";
        }

        Object details = authentication.getDetails();

        if (!(details instanceof WebAuthenticationDetails)) {
            return "";
        }

        WebAuthenticationDetails webDetails = (WebAuthenticationDetails) details;

        return webDetails.getRemoteAddress();
    }

    public String getSessionId(Authentication authentication) {
        if (authentication == null) {
            return "";
        }

        Object details = authentication.getDetails();

        if (!(details instanceof WebAuthenticationDetails)) {
            return "";
        }

        WebAuthenticationDetails webDetails = (WebAuthenticationDetails) details;

        return webDetails.getSessionId();
    }

    public String getTenantId(Authentication authentication) {
        if (authentication == null) {
            return "";
        }

        Object principal = authentication.getPrincipal();

        if (principal instanceof SpringSecurityUserAuth) {
            return ((SpringSecurityUserAuth) principal).getTenantId();
        } else {
            return "";
        }
    }

    @Resource
    public void setAuditConnector(AuditConnector auditConnector) {
        this.auditConnector = auditConnector;
    }

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.ctx = applicationContext;
    }
}
