package be.iminds.ilabt.jfed.lowlevel.user;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.lowlevel.AnyCredential;
import be.iminds.ilabt.jfed.lowlevel.GeniUser;
import be.iminds.ilabt.jfed.lowlevel.SimpleGeniUser;
import be.iminds.ilabt.jfed.lowlevel.SimpleGeniUserWithSpeaksFor;
import be.iminds.ilabt.jfed.lowlevel.authority.AuthorityFinder;
import be.iminds.ilabt.jfed.lowlevel.userloginmodel.InvalidLoginException;
import be.iminds.ilabt.jfed.lowlevel.userloginmodel.InvalidPasswordException;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.IOUtils;
import be.iminds.ilabt.jfed.util.KeyUtil;
import com.google.common.base.Charsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.security.PrivateKey;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * User: twalcari
 * Date: 7/14/2016
 * Time: 13:43
 */
@Singleton
public class GeniUserFactory {
    private static final Logger LOG = LoggerFactory.getLogger(GeniUserFactory.class);

    @Nullable
    private final AuthorityFinder authorityFinder;

    @Inject
    public GeniUserFactory(@Nullable AuthorityFinder authorityFinder) {
        this.authorityFinder = authorityFinder;
    }

    @Nullable
    public static GeniUrn deriveUserUrnFromCertificateChain(@Nonnull List<X509Certificate> certificateChain)
            throws CertificateParsingException {
        X509Certificate firstCertificate = certificateChain.get(0);
        final Collection<List<?>> altNames = firstCertificate.getSubjectAlternativeNames();

        if (altNames == null)
            return null;

        for (List<?> altName : altNames) {
            Integer nameType = (Integer) altName.get(0);
            if (nameType == 6) {
                //uniformResourceIdentifier
                String urn = (String) altName.get(1);
                GeniUrn geniUrn = GeniUrn.parse(urn);

                if (geniUrn != null && Objects.equals(geniUrn.getEncodedResourceType(), "user"))
                    return geniUrn;
            }
        }

        return null;
    }

    @Nonnull
    public GeniUser createGeniUser(@Nonnull String keyCertContent, char[] keyPass, File certificateKeyFile, File privateKeyFile, @Nullable List<AnyCredential> speaksForCredentials) throws InvalidLoginException, SimpleGeniUserWithSpeaksFor.InvalidSpeaksForException {
        GeniUser baseUser = createGeniUser(keyCertContent, keyPass, certificateKeyFile, privateKeyFile);
        if (speaksForCredentials == null || speaksForCredentials.isEmpty())
            return baseUser;
        return new SimpleGeniUserWithSpeaksFor(
                baseUser.getUserAuthorityServer(),
                baseUser.getUserUrn(),
                baseUser.getClientCertificateChain(),
                baseUser.getPrivateKey(),
                baseUser.getCertificateFile(),
                baseUser.getPrivateKeyFile(),
                speaksForCredentials);
    }

    @Nonnull
    public GeniUser createGeniUser(@Nonnull String keyCertContent, @Nullable char[] keyPass,
                                   @Nullable File certificateKeyFile, @Nullable File privateKeyFile)
            throws InvalidLoginException {

        PrivateKey privateKey;
        try {
            privateKey = KeyUtil.pemToAnyPrivateKey(keyCertContent, keyPass);
        } catch (KeyUtil.PEMDecodingException e) {
            throw new InvalidPasswordException("ERROR reading PEM key:" + keyCertContent + " -> " + e.getMessage(), e);
        }
        if (privateKey == null)
            throw new InvalidLoginException("ERROR: PEM key and certificate does not contain a key:" + keyCertContent);


        List<X509Certificate> certificateChain = KeyUtil.pemToX509CertificateChain(keyCertContent);
        if (certificateChain == null || certificateChain.isEmpty())
            throw new InvalidLoginException("ERROR: PEM key and certificate does not contain a X509 certificate:" + keyCertContent);

        GeniUrn userUrn;
        try {
            userUrn = deriveUserUrnFromCertificateChain(certificateChain);
        } catch (CertificateParsingException e) {
            throw new InvalidLoginException("Could not parse certificate to extract user URN", e);
        }
        if (userUrn == null)
            throw new InvalidLoginException("Could not find user URN in certificate");


        Server userAuthorityServer;
        if(authorityFinder != null)
            userAuthorityServer = authorityFinder.findByAnyUrn(userUrn, AuthorityFinder.Purpose.FIND_USERAUTH);
        else
            userAuthorityServer = null;

        if (userAuthorityServer == null)
            LOG.warn("Could not find user authority server for {}", userUrn);

        return new SimpleGeniUser(userAuthorityServer, userUrn, keyCertContent, keyPass, certificateKeyFile, privateKeyFile);
    }

    @Nonnull
    public GeniUser createGeniUser(@Nonnull File certificateKeyFile, @Nullable char[] keyPass)
            throws InvalidLoginException {

        if (!certificateKeyFile.exists())
            throw new InvalidLoginException("Key and Certificate file does not exist: \"" + certificateKeyFile.getPath() + "\"\n");

        try {
            return createGeniUser(IOUtils.fileToString(certificateKeyFile), keyPass, certificateKeyFile, certificateKeyFile);
        } catch (IOException e) {
            throw new InvalidLoginException("Could not read certificate file", e);
        }
    }

    @Nonnull
    public GeniUser createGeniUser(@Nonnull URL url, @Nullable char[] keyPass)
            throws InvalidLoginException {
        try {
            String keyCertContent = IOUtils.urlToString(url, Charsets.UTF_8);

            if (keyCertContent.isEmpty()) {
                throw new InvalidLoginException("Could not read key-certificate pair from the provided URL.");
            }

            return createGeniUser(keyCertContent, keyPass, null, null);
        } catch (IOException e) {
            LOG.error("Could not fetch key-certificate-pair", e);
            throw new InvalidLoginException("Could not fetch key-certificate-pair", e);
        }
    }


}