/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.syncope.core.logic.init;

import java.io.InputStream;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.PropertyUtils;
import org.apache.syncope.core.persistence.api.SyncopeLoader;
import org.apache.syncope.core.provisioning.api.EntitlementsHolder;
import org.apache.syncope.common.lib.types.SAML2SPEntitlement;
import org.apache.syncope.core.logic.saml2.SAML2IdPCache;
import org.apache.syncope.core.logic.saml2.SAML2ReaderWriter;
import org.apache.syncope.core.persistence.api.DomainsHolder;
import org.apache.syncope.core.persistence.api.dao.SAML2IdPDAO;
import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
import org.apache.syncope.core.spring.ApplicationContextProvider;
import org.apache.syncope.core.spring.ResourceWithFallbackLoader;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.wss4j.common.saml.OpenSAMLUtil;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.impl.KeyStoreCredentialResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class SAML2SPLoader implements SyncopeLoader {

    private static final Logger LOG = LoggerFactory.getLogger(SAML2SPLoader.class);

    private static final String SAML2SP_LOGIC_PROPERTIES = "saml2sp-logic.properties";

    private static <T> T assertNotNull(final T argument, final String name) {
        if (argument == null) {
            throw new IllegalArgumentException("Argument '" + name + "' may not be null.");
        }
        return argument;
    }

    static {
        OpenSAMLUtil.initSamlEngine(false);
    }

    @Autowired
    private SAML2ReaderWriter saml2rw;

    @Autowired
    private DomainsHolder domainsHolder;

    @Autowired
    private SAML2IdPCache cache;

    @Autowired
    private SAML2IdPDAO idpDAO;

    private boolean inited;

    private KeyStore keystore;

    private String keyPass;

    private Credential credential;

    private String signatureAlgorithm;

    @Override
    public Integer getPriority() {
        return 1000;
    }

    @Override
    public void load() {
        EntitlementsHolder.getInstance().init(SAML2SPEntitlement.values());

        Pair<Properties, String> init = PropertyUtils.read(getClass(), SAML2SP_LOGIC_PROPERTIES, "conf.directory");
        Properties props = init.getLeft();
        String confDirectory = init.getRight();

        assertNotNull(confDirectory, "<conf.directory>");

        String name = props.getProperty("keystore.name");
        assertNotNull(name, "<keystore.name>");
        String type = props.getProperty("keystore.type");
        assertNotNull(type, "<keystore.type>");
        String storePass = props.getProperty("keystore.storepass");
        assertNotNull(storePass, "<keystore.storepass>");
        keyPass = props.getProperty("keystore.keypass");
        assertNotNull(keyPass, "<keystore.keypass>");
        String certAlias = props.getProperty("sp.cert.alias");
        assertNotNull(certAlias, "<sp.cert.alias>");
        signatureAlgorithm = props.getProperty("signature.algorithm");

        LOG.debug("Attempting to load the provided keystore...");
        try {
            ResourceWithFallbackLoader loader = new ResourceWithFallbackLoader();
            loader.setResourceLoader(ApplicationContextProvider.getApplicationContext());
            loader.setPrimary(StringUtils.appendIfMissing("file:" + confDirectory, "/") + name);
            loader.setFallback("classpath:" + name);

            keystore = KeyStore.getInstance(type);
            try (InputStream inputStream = loader.getResource().getInputStream()) {
                keystore.load(inputStream, storePass.toCharArray());
                LOG.debug("Keystore loaded");
            }

            Map<String, String> passwordMap = new HashMap<>();
            passwordMap.put(certAlias, keyPass);
            KeyStoreCredentialResolver resolver = new KeyStoreCredentialResolver(keystore, passwordMap);

            this.credential = resolver.resolveSingle(new CriteriaSet(new EntityIdCriterion(certAlias)));
            LOG.debug("SAML 2.0 Service Provider certificate loaded");

            saml2rw.init();

            inited = true;
        } catch (Exception e) {
            LOG.error("Could not initialize the SAML 2.0 Service Provider certificate", e);
            inited = false;
        }

        for (String domain : domainsHolder.getDomains().keySet()) {
            AuthContextUtils.execWithAuthContext(domain, new AuthContextUtils.Executable<Void>() {

                @Override
                public Void exec() {
                    for (SAML2IdP idp : idpDAO.findAll()) {
                        try {
                            cache.put(idp);
                        } catch (Exception e) {
                            LOG.error("Could not cache the SAML 2.0 IdP with key ", idp.getEntityID(), e);
                        }
                    }
                    return null;
                }
            });
        }
    }

    public boolean isInited() {
        return inited;
    }

    public KeyStore getKeyStore() {
        return keystore;
    }

    public String getKeyPass() {
        return keyPass;
    }

    public Credential getCredential() {
        return credential;
    }

    public String getSignatureAlgorithm() {
        return signatureAlgorithm;
    }

}
