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

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

import javax.annotation.Nullable;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.List;

/**
 * User: twalcari
 * Date: 7/14/2016
 * Time: 15:00
 */
public abstract class AbstractUserLoginInfo implements UserLoginInfo {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractUserLoginInfo.class);

    private final boolean userAndAuthorityProvidedSeperately;

    @Nullable
    protected final String keyCertContent;

    protected final boolean unlocked;

    protected final List<X509Certificate> certificateChain;
    @Nullable
    protected final GeniUrn userUrn;
    @Nullable
    protected final Date expirationDate;

    @Nullable
    protected final Server userAuthorityServer;

    protected final GeniUserFactory geniUserFactory;

    protected String error = null;

    protected AbstractUserLoginInfo(@Nullable String keyCertContent, @Nullable GeniUrn userUrn,
                                    @Nullable Server userAuthorityServer, GeniUserFactory geniUserFactory) {
        this.userAndAuthorityProvidedSeperately = true;
        this.keyCertContent = keyCertContent;
        this.userUrn = userUrn;
        this.geniUserFactory = geniUserFactory;


        this.userAuthorityServer = userAuthorityServer;

        if (keyCertContent != null) {
            this.unlocked = !KeyUtil.isPemPrivateKeyEncrypted(keyCertContent);

            this.certificateChain = KeyUtil.pemToX509CertificateChain(keyCertContent);
            if (certificateChain == null)
                addError("Could not parse certificate chain");

            this.expirationDate = getEarliestExpire(certificateChain);
        } else {
            this.unlocked = false;
            this.certificateChain = null;
            this.expirationDate = null;
        }

    }


    protected AbstractUserLoginInfo(@Nullable String keyCertContent, GeniUserFactory geniUserFactory, AuthorityFinder authorityFinder) {
        this.userAndAuthorityProvidedSeperately = false;
        this.keyCertContent = keyCertContent;
        this.geniUserFactory = geniUserFactory;

        if (keyCertContent != null) {
            this.unlocked = !KeyUtil.isPemPrivateKeyEncrypted(keyCertContent);

            this.certificateChain = KeyUtil.pemToX509CertificateChain(keyCertContent);
            if (certificateChain == null)
                addError("Could not parse certificate chain");

            this.expirationDate = getEarliestExpire(certificateChain);
            GeniUrn userUrn = null;
            if (certificateChain != null) {
                try {
                    userUrn = GeniUserFactory.deriveUserUrnFromCertificateChain(certificateChain);
                } catch (CertificateParsingException e) {
                    LOG.error("Could not parse certificate chain. Will not extract user urn and expiration date", e);
                    addError("Could not parse certificate chain for user URN");
                }
            }
            this.userUrn = userUrn;

            if (userUrn != null)
                this.userAuthorityServer = authorityFinder.findByAnyUrn(userUrn, AuthorityFinder.Purpose.FIND_USERAUTH);
            else
                this.userAuthorityServer = null;
        } else {
            this.unlocked = false;
            this.certificateChain = null;
            this.userUrn = null;
            this.expirationDate = null;
            this.userAuthorityServer = null;

        }
    }

    private static Date getEarliestExpire(List<X509Certificate> certs) {
        if (certs == null)
            return null;

        Date res = null;
        for (X509Certificate cert : certs) {
            Date expire = cert.getNotAfter();
            if (res == null || res.after(expire))
                res = expire;
        }
        return res;
    }

    @Override
    public boolean isUserAndAuthorityProvidedSeperately() {
        return userAndAuthorityProvidedSeperately;
    }

    @Override
    public boolean isValid() {
        //userAuthorityServer is not required, so we don't check for that
        return certificateChain != null && userUrn != null && expirationDate != null;
    }

    @Override
    public boolean isUnlocked() {
        return unlocked;
    }

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

    @Nullable
    @Override
    public List<X509Certificate> getCertificateChain() {
        return certificateChain;
    }

    @Nullable
    @Override
    public Date getExpirationDate() {
        return expirationDate;
    }

    @Override
    public GeniUser login(@Nullable char[] password) throws InvalidLoginException {
        if (keyCertContent == null)
            throw new InvalidLoginException("Cannot login user with empty keyCertContent");
        return geniUserFactory.createGeniUser(keyCertContent, password, null, null);
    }

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


    @Override
    public String getCertificateContent() {
        return keyCertContent;
    }

    @Nullable
    @Override
    public String getError() {
        return error;
    }

    protected void addError(String newError) {
        if (error != null)
            this.error = error + "\n" + newError;
        else
            this.error = newError;
    }

    protected boolean equalInfo(UserLoginInfo o) {
        if (!Objects.equal(keyCertContent, o.getCertificateContent()))
            return false;

        if (!Objects.equal(userUrn, o.getUserUrn()))
            return false;

        if (!Objects.equal(userAuthorityServer, o.getUserAuthorityServer()))
            return false;

        return true;
    }
}
