package org.apereo.cas.support.oauth.web.endpoints;

import org.apereo.cas.authentication.principal.PrincipalFactory;
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.support.oauth.profile.OAuth20ProfileScopeToAttributesFilter;
import org.apereo.cas.support.oauth.validator.OAuth20Validator;
import org.apereo.cas.support.oauth.web.response.accesstoken.ext.AccessTokenRequestDataHolder;
import org.apereo.cas.ticket.OAuthToken;
import org.apereo.cas.ticket.TicketGrantingTicket;
import org.apereo.cas.ticket.TicketState;
import org.apereo.cas.ticket.accesstoken.AccessToken;
import org.apereo.cas.ticket.accesstoken.AccessTokenFactory;
import org.apereo.cas.ticket.code.OAuthCode;
import org.apereo.cas.ticket.registry.TicketRegistry;
import org.apereo.cas.web.support.CookieRetrievingCookieGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;

/**
 * This controller is the base controller for wrapping OAuth protocol in CAS.
 *
 * @author Jerome Leleu
 * @since 3.5.0
 */
@Controller
public abstract class BaseOAuth20Controller {
    private static final Logger LOGGER = LoggerFactory.getLogger(BaseOAuth20Controller.class);

    /**
     * Collection of CAS settings.
     */
    protected final CasConfigurationProperties casProperties;

    /**
     * Convert profile scopes to attributes.
     */
    protected final OAuth20ProfileScopeToAttributesFilter scopeToAttributesFilter;

    /**
     * Services manager.
     */
    protected final ServicesManager servicesManager;

    /**
     * Cookie retriever.
     */
    protected final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator;

    /**
     * The Ticket registry.
     */
    protected final TicketRegistry ticketRegistry;
    /**
     * The Validator.
     */
    protected final OAuth20Validator validator;
    /**
     * The Access token factory.
     */
    protected final AccessTokenFactory accessTokenFactory;
    /**
     * The Principal factory.
     */
    protected final PrincipalFactory principalFactory;
    /**
     * The Web application service service factory.
     */
    protected final ServiceFactory<WebApplicationService> webApplicationServiceServiceFactory;


    /**
     * Instantiates a new Base o auth 20 controller.
     *
     * @param servicesManager                     the services manager
     * @param ticketRegistry                      the ticket registry
     * @param validator                           the validator
     * @param accessTokenFactory                  the access token factory
     * @param principalFactory                    the principal factory
     * @param webApplicationServiceServiceFactory the web application service service factory
     * @param scopeToAttributesFilter             the scope to attributes filter
     * @param casProperties                       the cas properties
     * @param ticketGrantingTicketCookieGenerator the ticket granting ticket cookie generator
     */
    public BaseOAuth20Controller(final ServicesManager servicesManager,
                                 final TicketRegistry ticketRegistry,
                                 final OAuth20Validator validator,
                                 final AccessTokenFactory accessTokenFactory,
                                 final PrincipalFactory principalFactory,
                                 final ServiceFactory<WebApplicationService> webApplicationServiceServiceFactory,
                                 final OAuth20ProfileScopeToAttributesFilter scopeToAttributesFilter,
                                 final CasConfigurationProperties casProperties,
                                 final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator) {
        this.servicesManager = servicesManager;
        this.ticketRegistry = ticketRegistry;
        this.validator = validator;
        this.accessTokenFactory = accessTokenFactory;
        this.principalFactory = principalFactory;
        this.webApplicationServiceServiceFactory = webApplicationServiceServiceFactory;
        this.casProperties = casProperties;
        this.scopeToAttributesFilter = scopeToAttributesFilter;
        this.ticketGrantingTicketCookieGenerator = ticketGrantingTicketCookieGenerator;
    }


    /**
     * Generate an access token from a service and authentication.
     *
     * @param responseHolder the response holder
     * @return an access token
     */
    protected AccessToken generateAccessToken(final AccessTokenRequestDataHolder responseHolder) {
        LOGGER.debug("Creating refresh token for [{}]", responseHolder.getService());
        final AccessToken accessToken = this.accessTokenFactory.create(responseHolder.getService(),
                responseHolder.getAuthentication(), responseHolder.getTicketGrantingTicket());
        LOGGER.debug("Creating access token [{}]", accessToken);
        addTicketToRegistry(accessToken, responseHolder.getTicketGrantingTicket());
        LOGGER.debug("Added access token [{}] to registry", accessToken);
        
        if (responseHolder.getToken() instanceof OAuthCode) {
            final TicketState codeState = TicketState.class.cast(responseHolder.getToken());
            codeState.update();

            if (responseHolder.getToken().isExpired()) {
                this.ticketRegistry.deleteTicket(responseHolder.getToken().getId());
            } else {
                this.ticketRegistry.updateTicket(responseHolder.getToken());
            }
            this.ticketRegistry.updateTicket(responseHolder.getTicketGrantingTicket());
        }
        return accessToken;
    }

    /**
     * Add ticket to registry.
     *
     * @param ticket               the ticket
     * @param ticketGrantingTicket the ticket granting ticket
     */
    protected void addTicketToRegistry(final OAuthToken ticket, final TicketGrantingTicket ticketGrantingTicket) {
        LOGGER.debug("Adding OAuth ticket [{}] to registry", ticket);
        this.ticketRegistry.addTicket(ticket);
        if (ticketGrantingTicket != null) {
            LOGGER.debug("Updating ticket-granting ticket [{}]", ticketGrantingTicket);
            this.ticketRegistry.updateTicket(ticketGrantingTicket);
        }
    }
}
