package be.iminds.ilabt.jfed.lowlevel;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.lowlevel.user.GeniUserFactory;
import be.iminds.ilabt.jfed.lowlevel.userloginmodel.InvalidLoginException;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.KeyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Objects;

/**
 * SimpleGeniUser
 */
public class SimpleGeniUser implements GeniUser {
    private final static Logger LOG = LoggerFactory.getLogger(SimpleGeniUser.class);

    protected final List<X509Certificate> certificateChain;
    protected final PrivateKey privateKey;
    protected final PublicKey publicKey;

    protected final Server userAuthorityServer;
    protected final GeniUrn userUrn;

    protected final File privateKeyFile;
    protected final File certificateKeyFile;

    public SimpleGeniUser(@Nullable Server userAuthorityServer, @Nonnull GeniUrn userUrn,
                          @Nonnull String keyCertContent, @Nullable char[] keyPass,
                          @Nullable File certificateKeyFile, @Nullable File privateKeyFile)
            throws InvalidLoginException {
        this.userAuthorityServer = userAuthorityServer;
        this.userUrn = userUrn;
        this.certificateKeyFile = certificateKeyFile;
        this.privateKeyFile = privateKeyFile;

        PrivateKey privateKey;
        try {
            privateKey = KeyUtil.pemToAnyPrivateKey(keyCertContent, keyPass);
        } catch (KeyUtil.PEMDecodingException e) {
            throw new InvalidLoginException("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);

        this.privateKey = privateKey;

        certificateChain = KeyUtil.pemToX509CertificateChain(keyCertContent);
        if (certificateChain == null || certificateChain.isEmpty())
            throw new InvalidLoginException("ERROR: PEM key and certificate does not contain a X509 certificate:" + keyCertContent);
        this.publicKey = certificateChain.get(0).getPublicKey();

        try {
            assert Objects.equals(userUrn, GeniUserFactory.deriveUserUrnFromCertificateChain(certificateChain)) :
                    "Provided Urn doesn't match the one in the certificate chain";
        } catch (CertificateParsingException e) {
            throw new RuntimeException("Could not parse certificate while creating GeniUser", e);
        }
    }

    public SimpleGeniUser(@Nullable Server userAuthorityServer, @Nonnull GeniUrn userUrn,
                          @Nonnull List<X509Certificate> certificateChain, @Nonnull PrivateKey privateKey,
                          @Nullable File certificateKeyFile, @Nullable File privateKeyFile) {
        this.userAuthorityServer = userAuthorityServer;
        this.userUrn = userUrn;
        this.privateKey = privateKey;
        this.certificateChain = certificateChain;
        this.certificateKeyFile = certificateKeyFile;
        this.privateKeyFile = privateKeyFile;

        assert !certificateChain.isEmpty();
        this.publicKey = certificateChain.get(0).getPublicKey();


        try {
            assert Objects.equals(userUrn, GeniUserFactory.deriveUserUrnFromCertificateChain(certificateChain)) :
                    "Provided Urn doesn't match the one in the certificate chain";
        } catch (CertificateParsingException e) {
            throw new RuntimeException("Could not parse certificate while creating GeniUser", e);
        }
    }

    /**
     * make a copy of a GeniUser
     */
    public SimpleGeniUser(GeniUser geniUser) {
        //this constructor is also called from the copy-constructor of GeniUserWithSpeaksFor, so this doesn't make sense
        if (geniUser instanceof GeniUserWithSpeaksFor) {
            LOG.warn("Making a copy of a GeniUserWithSpeaksFor with SimpleGeniUser will DISCARD the speaks_for details.");
        }
        this.userAuthorityServer = geniUser.getUserAuthorityServer();
        this.userUrn = GeniUrn.parse(geniUser.getUserUrnString());
        this.publicKey = geniUser.getPublicKey();
        this.privateKey = geniUser.getPrivateKey();
        this.privateKeyFile = geniUser.getPrivateKeyFile();

        this.certificateChain = geniUser.getClientCertificateChain();
        this.certificateKeyFile = geniUser.getCertificateFile();

        assert !certificateChain.isEmpty();
        assert Objects.equals(certificateChain.get(0).getPublicKey(), publicKey);
    }

    @Nonnull
    @Override
    public List<X509Certificate> getClientCertificateChain() {
        return certificateChain;
    }

    @Nonnull
    @Override
    public PrivateKey getPrivateKey() {
        return privateKey;
    }

    @Nonnull
    @Override
    public PublicKey getPublicKey() {
        return publicKey;
    }

    @Override
    public Server getUserAuthorityServer() {
        return userAuthorityServer;
    }

    @Nonnull
    @Override
    public GeniUrn getUserUrn() {
        return userUrn;
    }

    @Override
    public File getPrivateKeyFile() {
        return privateKeyFile;
    }

    @Override
    public File getCertificateFile() {
        return certificateKeyFile;
    }

    @Nonnull
    @Override
    public String getUserUrnString() {
        return userUrn.toString();
    }

}
