package org.apereo.cas.web.flow;

import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.CasProtocolConstants;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.authentication.principal.ServiceFactory;
import org.apereo.cas.authentication.principal.WebApplicationService;
import org.apereo.cas.configuration.model.core.logout.LogoutProperties;
import org.apereo.cas.logout.LogoutRequest;
import org.apereo.cas.logout.LogoutRequestStatus;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * Action to delete the TGT and the appropriate cookies.
 * It also performs the back-channel SLO on the services accessed by the user during its browsing.
 * After this back-channel SLO, a front-channel SLO can be started if some services require it.
 * The final logout page or a redirection url is also computed in this action.
 *
 * @author Scott Battaglia
 * @author Jerome Leleu
 * @since 3.0.0
 */
public class LogoutAction extends AbstractLogoutAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(LogoutAction.class);

    private final ServiceFactory<WebApplicationService> webApplicationServiceFactory;

    private final ServicesManager servicesManager;

    private final LogoutProperties logoutProperties;

    public LogoutAction(final ServiceFactory<WebApplicationService> webApplicationServiceFactory,
                        final ServicesManager servicesManager,
                        final LogoutProperties logoutProperties) {
        this.webApplicationServiceFactory = webApplicationServiceFactory;
        this.servicesManager = servicesManager;
        this.logoutProperties = logoutProperties;
    }

    @Override
    protected Event doInternalExecute(final HttpServletRequest request, final HttpServletResponse response,
                                      final RequestContext context) throws Exception {

        boolean needFrontSlo = false;
        final List<LogoutRequest> logoutRequests = WebUtils.getLogoutRequests(context);
        if (logoutRequests != null) {
            needFrontSlo = logoutRequests
                    .stream()
                    .anyMatch(logoutRequest -> logoutRequest.getStatus() == LogoutRequestStatus.NOT_ATTEMPTED);
        }

        final String paramName = StringUtils.defaultIfEmpty(logoutProperties.getRedirectParameter(), CasProtocolConstants.PARAMETER_SERVICE);
        LOGGER.debug("Using parameter name [{}] to detect destination service, if any", paramName);
        final String service = request.getParameter(paramName);
        LOGGER.debug("Located target service [{}] for redirection after logout", paramName);

        if (logoutProperties.isFollowServiceRedirects() && StringUtils.isNotBlank(service)) {
            final Service webAppService = webApplicationServiceFactory.createService(service);
            final RegisteredService rService = this.servicesManager.findServiceBy(webAppService);

            if (rService != null && rService.getAccessStrategy().isServiceAccessAllowed()) {
                LOGGER.debug("Redirecting to service [{}]", service);
                WebUtils.putLogoutRedirectUrl(context, service);
            } else {
                LOGGER.warn("Cannot redirect to [{}] given the service is unauthorized to use CAS. "
                        + "Ensure the service is registered with CAS and is enabled to allowed access", service);
            }
        } else {
            LOGGER.debug("No target service is located for redirection after logout, or CAS is not allowed to follow redirects after logout");
        }

        // there are some front services to logout, perform front SLO
        if (needFrontSlo) {
            LOGGER.debug("Proceeding forward with front-channel single logout");
            return new Event(this, FRONT_EVENT);
        }
        LOGGER.debug("Moving forward to finish the logout process");
        return new Event(this, FINISH_EVENT);
    }
}
