package org.apereo.cas.support.saml.services;

import com.google.common.base.Throwables;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apereo.cas.services.AbstractRegisteredService;
import org.apereo.cas.services.RegexRegisteredService;
import org.apereo.cas.services.RegisteredService;

import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.MapKeyColumn;
import java.util.Map;
import java.util.TreeMap;

/**
 * The {@link SamlRegisteredService} is responsible for managing the SAML metadata for a given SP.
 *
 * @author Misagh Moayyed
 * @since 5.0.0
 */
@Entity
@DiscriminatorValue("saml")
public class SamlRegisteredService extends RegexRegisteredService {
    private static final long serialVersionUID = 1218757374062931021L;

    private String metadataLocation;

    /**
     * Defines a filter that requires the presence of a validUntil
     * attribute on the root element of the metadata document.
     * A maximum validity interval of less than 1 means that
     * no restriction is placed on the metadata's validUntil attribute.
     */
    @Column(updatable = true, insertable = true)
    private long metadataMaxValidity;

    @Column(length = 255, updatable = true, insertable = true)
    private String requiredAuthenticationContextClass;

    @Column(length = 255, updatable = true, insertable = true)
    private String metadataCriteriaDirection;

    @Column(length = 255, updatable = true, insertable = true)
    private String metadataCriteriaPattern;

    @Column(length = 255, updatable = true, insertable = true)
    private String requiredNameIdFormat;

    @Column(length = 255, updatable = true, insertable = true)
    private String metadataSignatureLocation;

    @Column(length = 255, updatable = true, insertable = true)
    private String serviceProviderNameIdQualifier;

    @Column(length = 255, updatable = true, insertable = true)
    private String nameIdQualifier;

    @Column(updatable = true, insertable = true)
    private boolean signAssertions;

    @Column(updatable = true, insertable = true)
    private boolean signResponses = true;

    @Column(updatable = true, insertable = true)
    private boolean encryptAssertions;

    @Column(length = 255, updatable = true, insertable = true)
    private String metadataCriteriaRoles = "SPSSODescriptor";

    @Column(updatable = true, insertable = true)
    private boolean metadataCriteriaRemoveEmptyEntitiesDescriptors = true;

    @Column(updatable = true, insertable = true)
    private boolean metadataCriteriaRemoveRolelessEntityDescriptors = true;

    @ElementCollection
    @CollectionTable(name = "SamlRegisteredService_AttributeNameFormats")
    @MapKeyColumn(name = "key")
    @Column(name = "value")
    private Map<String, String> attributeNameFormats = new TreeMap<>();

    /**
     * Instantiates a new Saml registered service.
     */
    public SamlRegisteredService() {
        super();
    }

    public void setMetadataLocation(final String metadataLocation) {
        this.metadataLocation = metadataLocation;
    }

    public String getMetadataLocation() {
        return this.metadataLocation;
    }

    public boolean isSignAssertions() {
        return this.signAssertions;
    }

    public void setSignAssertions(final boolean signAssertions) {
        this.signAssertions = signAssertions;
    }

    public boolean isSignResponses() {
        return this.signResponses;
    }

    public void setSignResponses(final boolean signResponses) {
        this.signResponses = signResponses;
    }

    public String getRequiredAuthenticationContextClass() {
        return this.requiredAuthenticationContextClass;
    }

    public void setRequiredAuthenticationContextClass(final String requiredAuthenticationContextClass) {
        this.requiredAuthenticationContextClass = requiredAuthenticationContextClass;
    }

    public String getMetadataSignatureLocation() {
        return this.metadataSignatureLocation;
    }

    public void setMetadataSignatureLocation(final String metadataSignatureLocation) {
        this.metadataSignatureLocation = metadataSignatureLocation;
    }

    public boolean isEncryptAssertions() {
        return this.encryptAssertions;
    }

    public void setEncryptAssertions(final boolean encryptAssertions) {
        this.encryptAssertions = encryptAssertions;
    }

    public long getMetadataMaxValidity() {
        return this.metadataMaxValidity;
    }

    public void setMetadataMaxValidity(final long metadataMaxValidity) {
        this.metadataMaxValidity = metadataMaxValidity;
    }

    public String getMetadataCriteriaDirection() {
        return metadataCriteriaDirection;
    }

    public void setMetadataCriteriaDirection(final String metadataCriteriaDirection) {
        this.metadataCriteriaDirection = metadataCriteriaDirection;
    }

    public String getMetadataCriteriaPattern() {
        return metadataCriteriaPattern;
    }

    public void setMetadataCriteriaPattern(final String metadataCriteriaPattern) {
        this.metadataCriteriaPattern = metadataCriteriaPattern;
    }

    public String getRequiredNameIdFormat() {
        return requiredNameIdFormat;
    }

    public void setRequiredNameIdFormat(final String requiredNameIdFormat) {
        this.requiredNameIdFormat = requiredNameIdFormat;
    }

    public String getMetadataCriteriaRoles() {
        return metadataCriteriaRoles;
    }

    public void setMetadataCriteriaRoles(final String metadataCriteriaRole) {
        this.metadataCriteriaRoles = metadataCriteriaRole;
    }

    public boolean isMetadataCriteriaRemoveEmptyEntitiesDescriptors() {
        return metadataCriteriaRemoveEmptyEntitiesDescriptors;
    }

    public void setMetadataCriteriaRemoveEmptyEntitiesDescriptors(final boolean metadataCriteriaRemoveEmptyEntitiesDescriptors) {
        this.metadataCriteriaRemoveEmptyEntitiesDescriptors = metadataCriteriaRemoveEmptyEntitiesDescriptors;
    }

    public boolean isMetadataCriteriaRemoveRolelessEntityDescriptors() {
        return metadataCriteriaRemoveRolelessEntityDescriptors;
    }

    public void setMetadataCriteriaRemoveRolelessEntityDescriptors(final boolean metadataCriteriaRemoveRolelessEntityDescriptors) {
        this.metadataCriteriaRemoveRolelessEntityDescriptors = metadataCriteriaRemoveRolelessEntityDescriptors;
    }

    public Map<String, String> getAttributeNameFormats() {
        return attributeNameFormats;
    }

    public void setAttributeNameFormats(final Map<String, String> attributeNameFormats) {
        this.attributeNameFormats = attributeNameFormats;
    }

    public String getServiceProviderNameIdQualifier() {
        return serviceProviderNameIdQualifier;
    }

    public void setServiceProviderNameIdQualifier(final String serviceProviderNameIdQualifier) {
        this.serviceProviderNameIdQualifier = serviceProviderNameIdQualifier;
    }

    public String getNameIdQualifier() {
        return nameIdQualifier;
    }

    public void setNameIdQualifier(final String nameIdQualifier) {
        this.nameIdQualifier = nameIdQualifier;
    }

    @Override
    protected AbstractRegisteredService newInstance() {
        return new SamlRegisteredService();
    }

    @Override
    public void copyFrom(final RegisteredService source) {
        super.copyFrom(source);
        try {
            final SamlRegisteredService samlRegisteredService = (SamlRegisteredService) source;
            setMetadataLocation(samlRegisteredService.getMetadataLocation());
            setSignAssertions(samlRegisteredService.isSignAssertions());
            setSignResponses(samlRegisteredService.isSignResponses());
            setRequiredAuthenticationContextClass(samlRegisteredService.getRequiredAuthenticationContextClass());
            setMetadataMaxValidity(samlRegisteredService.getMetadataMaxValidity());
            setMetadataSignatureLocation(samlRegisteredService.getMetadataSignatureLocation());
            setEncryptAssertions(samlRegisteredService.isEncryptAssertions());
            setRequiredNameIdFormat(samlRegisteredService.getRequiredNameIdFormat());

            setMetadataCriteriaDirection(samlRegisteredService.getMetadataCriteriaDirection());
            setMetadataCriteriaPattern(samlRegisteredService.getMetadataCriteriaPattern());

            setMetadataCriteriaRemoveEmptyEntitiesDescriptors(samlRegisteredService.isMetadataCriteriaRemoveEmptyEntitiesDescriptors());
            setMetadataCriteriaRemoveRolelessEntityDescriptors(samlRegisteredService.isMetadataCriteriaRemoveRolelessEntityDescriptors());
            setMetadataCriteriaRoles(samlRegisteredService.getMetadataCriteriaRoles());
            setAttributeNameFormats(samlRegisteredService.getAttributeNameFormats());

            setNameIdQualifier(samlRegisteredService.getNameIdQualifier());
            setServiceProviderNameIdQualifier(samlRegisteredService.serviceProviderNameIdQualifier);

        } catch (final Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        if (obj.getClass() != getClass()) {
            return false;
        }
        final SamlRegisteredService rhs = (SamlRegisteredService) obj;
        return new EqualsBuilder()
                .appendSuper(super.equals(obj))
                .append(this.metadataLocation, rhs.metadataLocation)
                .append(this.metadataMaxValidity, rhs.metadataMaxValidity)
                .append(this.requiredAuthenticationContextClass, rhs.requiredAuthenticationContextClass)
                .append(this.metadataSignatureLocation, rhs.metadataSignatureLocation)
                .append(this.signAssertions, rhs.signAssertions)
                .append(this.signResponses, rhs.signResponses)
                .append(this.encryptAssertions, rhs.encryptAssertions)
                .append(this.requiredNameIdFormat, rhs.requiredNameIdFormat)
                .append(this.metadataCriteriaDirection, rhs.metadataCriteriaDirection)
                .append(this.metadataCriteriaPattern, rhs.metadataCriteriaPattern)
                .append(this.metadataCriteriaRemoveEmptyEntitiesDescriptors, rhs.metadataCriteriaRemoveEmptyEntitiesDescriptors)
                .append(this.metadataCriteriaRemoveRolelessEntityDescriptors, rhs.metadataCriteriaRemoveRolelessEntityDescriptors)
                .append(this.metadataCriteriaRoles, rhs.metadataCriteriaRoles)
                .append(this.attributeNameFormats, rhs.attributeNameFormats)
                .append(this.serviceProviderNameIdQualifier, rhs.serviceProviderNameIdQualifier)
                .append(this.nameIdQualifier, rhs.nameIdQualifier)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .appendSuper(super.hashCode())
                .append(this.metadataLocation)
                .append(this.metadataMaxValidity)
                .append(this.requiredAuthenticationContextClass)
                .append(this.metadataSignatureLocation)
                .append(this.signAssertions)
                .append(this.signResponses)
                .append(this.encryptAssertions)
                .append(this.requiredNameIdFormat)
                .append(this.metadataCriteriaDirection)
                .append(this.metadataCriteriaPattern)
                .append(this.metadataCriteriaRemoveEmptyEntitiesDescriptors)
                .append(this.metadataCriteriaRemoveRolelessEntityDescriptors)
                .append(this.metadataCriteriaRoles)
                .append(this.attributeNameFormats)
                .append(this.serviceProviderNameIdQualifier)
                .append(this.nameIdQualifier)
                .toHashCode();
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .appendSuper(super.toString())
                .append("metadataLocation", this.metadataLocation)
                .append("metadataMaxValidity", this.metadataMaxValidity)
                .append("requiredAuthenticationContextClass", this.requiredAuthenticationContextClass)
                .append("metadataSignatureLocation", this.metadataSignatureLocation)
                .append("signAssertions", this.signAssertions)
                .append("signResponses", this.signResponses)
                .append("encryptAssertions", this.encryptAssertions)
                .append("requiredNameIdFormat", this.requiredNameIdFormat)
                .append("metadataCriteriaDirection", this.metadataCriteriaDirection)
                .append("metadataCriteriaPattern", this.metadataCriteriaPattern)
                .append("metadataCriteriaRemoveEmptyEntitiesDescriptors", this.metadataCriteriaRemoveEmptyEntitiesDescriptors)
                .append("metadataCriteriaRemoveRolelessEntityDescriptors", this.metadataCriteriaRemoveRolelessEntityDescriptors)
                .append("metadataCriteriaRoles", this.metadataCriteriaRoles)
                .append("attributeNameFormats", this.attributeNameFormats)
                .append("serviceProviderNameIdQualifier", this.serviceProviderNameIdQualifier)
                .append("nameIdQualifier", this.nameIdQualifier)
                .toString();
    }
}
