package org.apereo.cas.adaptors.ldap.services;

import com.google.common.base.Throwables;
import org.apereo.cas.configuration.model.support.ldap.serviceregistry.LdapServiceRegistryProperties;
import org.apereo.cas.services.AbstractRegisteredService;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.util.LdapUtils;
import org.apereo.cas.util.serialization.StringSerializer;
import org.apereo.cas.util.services.RegisteredServiceJsonSerializer;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;

/**
 * Default implementation of {@link LdapRegisteredServiceMapper} that is able
 * to map ldap entries to {@link RegisteredService} instances based on
 * certain attributes names. This implementation also respects the object class
 * attribute of LDAP entries via {@link LdapUtils#OBJECTCLASS_ATTRIBUTE}.
 *
 * @author Misagh Moayyed
 * @since 4.1.0
 */
public class DefaultLdapRegisteredServiceMapper implements LdapRegisteredServiceMapper {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLdapRegisteredServiceMapper.class);
    private final LdapServiceRegistryProperties ldap;

    private StringSerializer<RegisteredService> jsonSerializer = new RegisteredServiceJsonSerializer();

    public DefaultLdapRegisteredServiceMapper(final LdapServiceRegistryProperties ldapProperties) {
        ldap = ldapProperties;
    }

    @Override
    public LdapEntry mapFromRegisteredService(final String dn, final RegisteredService svc) {
        try {
            if (svc.getId() == RegisteredService.INITIAL_IDENTIFIER_VALUE) {
                ((AbstractRegisteredService) svc).setId(System.currentTimeMillis());
            }
            final String newDn = getDnForRegisteredService(dn, svc);
            LOGGER.debug("Creating entry [{}]", newDn);

            
            final Collection<LdapAttribute> attrs = new ArrayList<>();
            attrs.add(new LdapAttribute(ldap.getIdAttribute(), String.valueOf(svc.getId())));

            final StringWriter writer = new StringWriter();
            this.jsonSerializer.to(writer, svc);
            attrs.add(new LdapAttribute(ldap.getServiceDefinitionAttribute(), writer.toString()));
            attrs.add(new LdapAttribute(LdapUtils.OBJECTCLASS_ATTRIBUTE, "top", ldap.getObjectClass()));

            return new LdapEntry(newDn, attrs);
        } catch (final Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public RegisteredService mapToRegisteredService(final LdapEntry entry) {
        try {
            final String value = LdapUtils.getString(entry, ldap.getServiceDefinitionAttribute());
            if (StringUtils.hasText(value)) {
                return this.jsonSerializer.from(value);
            }

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

    @Override
    public String getObjectClass() {
        return ldap.getObjectClass();
    }

    @Override
    public String getIdAttribute() {
        return ldap.getIdAttribute();
    }


    public void setJsonSerializer(final StringSerializer<RegisteredService> jsonSerializer) {
        this.jsonSerializer = jsonSerializer;
    }

    @Override
    public String getDnForRegisteredService(final String parentDn, final RegisteredService svc) {
        return String.format("%s=%s,%s", ldap.getIdAttribute(), svc.getId(), parentDn);
    }
}
