package org.apereo.cas.services;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.util.spring.ApplicationContextProvider;
import org.apereo.services.persondir.util.CaseCanonicalizationMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import javax.persistence.PostLoad;
import java.util.Locale;

/**
 * This is {@link BaseRegisteredServiceUsernameAttributeProvider}.
 *
 * @author Misagh Moayyed
 * @since 5.1.0
 */
public abstract class BaseRegisteredServiceUsernameAttributeProvider implements RegisteredServiceUsernameAttributeProvider {
    private static final long serialVersionUID = -8381275200333399951L;
    private static final Logger LOGGER = LoggerFactory.getLogger(BaseRegisteredServiceUsernameAttributeProvider.class);

    private String canonicalizationMode = CaseCanonicalizationMode.NONE.name();
    private boolean encryptUsername;

    public BaseRegisteredServiceUsernameAttributeProvider() {
        setCanonicalizationMode(CaseCanonicalizationMode.NONE.name());
    }

    public BaseRegisteredServiceUsernameAttributeProvider(final String canonicalizationMode) {
        this.canonicalizationMode = canonicalizationMode;
    }

    @Override
    public final String resolveUsername(final Principal principal, final Service service, final RegisteredService registeredService) {
        final String username = resolveUsernameInternal(principal, service);
        if (canonicalizationMode == null) {
            canonicalizationMode = CaseCanonicalizationMode.NONE.name();
        }

        final String uid = CaseCanonicalizationMode.valueOf(canonicalizationMode).canonicalize(username.trim(), Locale.getDefault());
        LOGGER.debug("Resolved username for [{}] is [{}]", service.getId(), uid);

        if (!this.encryptUsername) {
            return uid;
        }
        final String encryptedId = encryptResolvedUsername(principal, service, registeredService, uid);
        if (StringUtils.isBlank(encryptedId)) {
            throw new IllegalArgumentException("Could not encrypt username " + uid + " for service " + service);
        }
        return encryptedId;
    }

    /**
     * Encrypt resolved username.
     *
     * @param principal         the principal
     * @param service           the service
     * @param registeredService the registered service
     * @param username          the username
     * @return the encrypted username or null
     */
    protected String encryptResolvedUsername(final Principal principal, final Service service,
                                             final RegisteredService registeredService, final String username) {
        final ApplicationContext applicationContext = ApplicationContextProvider.getApplicationContext();
        final RegisteredServiceCipherExecutor cipher = applicationContext.getBean("registeredServiceCipherExecutor", RegisteredServiceCipherExecutor.class);
        return cipher.encode(username, registeredService);
    }

    /**
     * Initializes the registered service with default values
     * for fields that are unspecified. Only triggered by JPA.
     */
    @PostLoad
    public void initialize() {
        setCanonicalizationMode(CaseCanonicalizationMode.NONE.name());
    }

    /**
     * Resolve username internal string.
     *
     * @param principal the principal
     * @param service   the service
     * @return the string
     */
    protected abstract String resolveUsernameInternal(Principal principal, Service service);

    public String getCanonicalizationMode() {
        return canonicalizationMode;
    }

    public void setCanonicalizationMode(final String canonicalizationMode) {
        this.canonicalizationMode = canonicalizationMode;
    }

    public boolean isEncryptUsername() {
        return encryptUsername;
    }

    public void setEncryptUsername(final boolean encryptUsername) {
        this.encryptUsername = encryptUsername;
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        if (obj.getClass() != getClass()) {
            return false;
        }
        final BaseRegisteredServiceUsernameAttributeProvider rhs = (BaseRegisteredServiceUsernameAttributeProvider) obj;
        return new EqualsBuilder()
                .append(this.canonicalizationMode, rhs.canonicalizationMode)
                .append(this.encryptUsername, rhs.encryptUsername)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .append(canonicalizationMode)
                .append(encryptUsername)
                .toHashCode();
    }
}
