package org.apereo.cas.ws.idp.web;

import com.google.common.base.Throwables;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.CasProtocolConstants;
import org.apereo.cas.authentication.AuthenticationServiceSelectionStrategy;
import org.apereo.cas.authentication.adaptive.UnauthorizedAuthenticationException;
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.CasConfigurationProperties;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.SecurityTokenTicketFactory;
import org.apereo.cas.ticket.registry.TicketRegistry;
import org.apereo.cas.ticket.registry.TicketRegistrySupport;
import org.apereo.cas.util.http.HttpClient;
import org.apereo.cas.web.support.CookieRetrievingCookieGenerator;
import org.apereo.cas.ws.idp.WSFederationConstants;
import org.apereo.cas.ws.idp.services.WSFederationRegisteredService;
import org.jasig.cas.client.authentication.AuthenticationRedirectStrategy;
import org.jasig.cas.client.authentication.DefaultAuthenticationRedirectStrategy;
import org.jasig.cas.client.util.CommonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;

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

/**
 * This is {@link WSFederationValidateRequestController}.
 *
 * @author Misagh Moayyed
 * @since 5.1.0
 */
public class WSFederationValidateRequestController extends BaseWSFederationRequestController {
    private static final Logger LOGGER = LoggerFactory.getLogger(WSFederationValidateRequestController.class);

    public WSFederationValidateRequestController(
            final ServicesManager servicesManager,
            final ServiceFactory<WebApplicationService> webApplicationServiceFactory,
            final CasConfigurationProperties casProperties,
            final AuthenticationServiceSelectionStrategy serviceSelectionStrategy,
            final HttpClient httpClient,
            final SecurityTokenTicketFactory securityTokenTicketFactory,
            final TicketRegistry ticketRegistry,
            final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator,
            final TicketRegistrySupport ticketRegistrySupport) {
        super(servicesManager,
                webApplicationServiceFactory, casProperties, serviceSelectionStrategy, httpClient,
                securityTokenTicketFactory, ticketRegistry, ticketGrantingTicketCookieGenerator, ticketRegistrySupport);
    }

    /**
     * Handle federation request.
     *
     * @param response the response
     * @param request  the request
     * @throws Exception the exception
     */
    @GetMapping(path = WSFederationConstants.ENDPOINT_FEDERATION_REQUEST)
    protected void handleFederationRequest(final HttpServletResponse response, final HttpServletRequest request) throws Exception {
        final WSFederationRequest fedRequest = WSFederationRequest.of(request);
        switch (fedRequest.getWa().toLowerCase()) {
            case WSFederationConstants.WSIGNOUT10:
            case WSFederationConstants.WSIGNOUT_CLEANUP10:
                handleLogoutRequest(fedRequest, request, response);
                break;
            case WSFederationConstants.WSIGNIN10:
                handleInitialAuthenticationRequest(fedRequest, response, request);
                break;
            default:
                throw new UnauthorizedAuthenticationException("The authentication request is not recognized",
                        Collections.emptyMap());
        }
    }

    private void handleLogoutRequest(final WSFederationRequest fedRequest, final HttpServletRequest request,
                                     final HttpServletResponse response) throws Exception {
        String logoutUrl = casProperties.getServer().getLogoutUrl();
        if (StringUtils.isNotBlank(fedRequest.getWreply())) {
            final Service service = webApplicationServiceFactory.createService(fedRequest.getWreply());
            final WSFederationRegisteredService registeredService = getWsFederationRegisteredService(service);
            LOGGER.debug("Invoking logout operation for request [{}], redirecting next to [{}] matched against [{}]",
                    fedRequest, fedRequest.getWreply(), registeredService);
            final String logoutParam = casProperties.getLogout().getRedirectParameter();
            logoutUrl = logoutUrl.concat("?").concat(logoutParam).concat("=").concat(service.getId());
        }
        final AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy();
        authenticationRedirectStrategy.redirect(request, response, logoutUrl);
    }

    private void handleInitialAuthenticationRequest(final WSFederationRequest fedRequest,
                                                    final HttpServletResponse response, final HttpServletRequest request) {
        final WSFederationRegisteredService service = findAndValidateFederationRequestForRegisteredService(response, request, fedRequest);
        LOGGER.debug("Redirecting to identity provider for initial authentication [{}]", fedRequest);
        redirectToIdentityProvider(fedRequest, response, request, service);
    }

    private void redirectToIdentityProvider(final WSFederationRequest fedRequest, final HttpServletResponse response,
                                            final HttpServletRequest request, final WSFederationRegisteredService service) {
        try {
            final String serviceUrl = constructServiceUrl(request, response, fedRequest);
            LOGGER.debug("Created service url [{}] mapped to [{}]", serviceUrl, service);
            final boolean renew = shouldRenewAuthentication(fedRequest, request);
            final String initialUrl = CommonUtils.constructRedirectUrl(casProperties.getServer().getLoginUrl(),
                    CasProtocolConstants.PARAMETER_SERVICE, serviceUrl, renew, false);
            LOGGER.debug("Redirecting authN request to [{}]", initialUrl);
            final AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy();
            authenticationRedirectStrategy.redirect(request, response, initialUrl);
        } catch (final Exception e) {
            throw Throwables.propagate(e);
        }
    }
}
