package be.iminds.ilabt.jfed.ui.commandline;

import be.iminds.ilabt.jfed.lowlevel.GeniUser;
import be.iminds.ilabt.jfed.lowlevel.RetrySettings;
import be.iminds.ilabt.jfed.lowlevel.SimpleGeniUser;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.authority.AuthorityFinder;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.lowlevel.userloginmodel.InvalidLoginException;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.IOUtils;
import be.iminds.ilabt.jfed.util.TargetAuthority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.util.Properties;

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

    private final Properties prop;
    private final TestbedInfoSource testbedInfoSource;
    private final AuthorityFinder authorityFinder;

    @Inject
    public ContextFile(@Named("contextFileProperties") Properties prop, TestbedInfoSource testbedInfoSource, AuthorityFinder authorityFinder) {
        this.prop = prop;
        this.testbedInfoSource = testbedInfoSource;
        this.authorityFinder = authorityFinder;
        LOG.debug("ContextFile has TestbedInfoSource with "+testbedInfoSource.getServers().size()+" entries");
    }

//    public static ContextFile loadFromFile(File file, TestbedInfoSource testbedInfoSource) throws IOException {
//        Properties prop = new Properties();
//        prop.load(new FileInputStream(file));
//        return new ContextFile(prop, testbedInfoSource);
//    }

    public GeniUser getUser() throws ContextFileException {
        return getUser("");
    }
    public GeniUser getUser(String userSuffix) throws ContextFileException {
        assert userSuffix != null;

        String passwordFilename = prop.getProperty("passwordFilename" + userSuffix);
        String userUrnString = prop.getProperty("userUrn" + userSuffix);
        String pemKeyAndCertFilename = prop.getProperty("pemKeyAndCertFilename" + userSuffix);
        String userAuthorityUrnString = prop.getProperty("userAuthorityUrn" + userSuffix);

        if (passwordFilename != null) passwordFilename = passwordFilename.trim();
        if (userUrnString != null) userUrnString = userUrnString.trim();
        if (pemKeyAndCertFilename != null) pemKeyAndCertFilename = pemKeyAndCertFilename.trim();
        if (userAuthorityUrnString != null) userAuthorityUrnString = userAuthorityUrnString.trim();

        if (userAuthorityUrnString == null || userAuthorityUrnString.isEmpty())
            throw new ContextFileException("property \"userAuthorityUrn" + userSuffix + "\" is missing");
        if (pemKeyAndCertFilename == null || pemKeyAndCertFilename.isEmpty())
            throw new ContextFileException("property \"pemKeyAndCertFilename" + userSuffix + "\" is missing");


        if (userUrnString == null) {
            String username = prop.getProperty("username" + userSuffix);
            if (username == null || username.isEmpty())
                throw new ContextFileException("property \"userUrn" + userSuffix + "\" (and alternative \"username" + userSuffix + "\") is missing");

//            if (userAuthorityUrn == null)
//                throw new InformationMissingException("property \"userUrn" + userSuffix + "\" is missing. Alternative " +
//                        "\"username" + userSuffix + "\" is present but userAuthorityUrn" + userSuffix + " is missing. ");
            GeniUrn authUrn = GeniUrn.parse(userAuthorityUrnString);
            if (authUrn == null)
                throw new ContextFileException("property \"userAuthorityUrn" + userSuffix + "\" is an invalid urn: \"" + userAuthorityUrnString + "\"");
            assert authUrn != null;
            userUrnString = GeniUrn.createGeniUrnFromEncodedParts(authUrn.getEncodedTopLevelAuthority(), "user", username).toString();
        }

        final GeniUrn userUrn;
        try {
            userUrn = new GeniUrn(userUrnString);
        } catch (GeniUrn.GeniUrnParseException e) {
            throw new ContextFileException("user urn is not valid: \""+userUrnString+"\"", e);
        }
        final GeniUrn userAuthorityUrn;
        try {
            userAuthorityUrn = new GeniUrn(userAuthorityUrnString);
        } catch (GeniUrn.GeniUrnParseException e) {
            throw new ContextFileException("user urn is not valid: \""+userUrnString+"\"", e);
        }

        assert userAuthorityUrn != null;

        Server userServer = authorityFinder.findByUrn(userAuthorityUrn, AuthorityFinder.Purpose.FIND_USERAUTH);

        if (userServer == null)
            throw new ContextFileException("User authority was not found: \""+userAuthorityUrn+"\"");

        String pemKeyAndCert = null;
        try {
            pemKeyAndCert = IOUtils.fileToString(pemKeyAndCertFilename);
        } catch (IOException e) {
            throw new ContextFileException("IOException reading PEM key and certificate file: " + e.getMessage(), e);
        }

        assert pemKeyAndCert != null;
        String password = null;
        if (passwordFilename != null && !passwordFilename.isEmpty())
            try {
                password = IOUtils.fileToString(passwordFilename).replaceAll("\n", "");
                assert password != null;
            } catch (IOException e) {
                throw new ContextFileException("Error reading password from file \"" + passwordFilename + "\"", e);
            }

        try {
            return new SimpleGeniUser(userServer, userUrn, pemKeyAndCert,
                    password == null ? null : password.toCharArray(),
                    new File(pemKeyAndCertFilename), new File(pemKeyAndCertFilename));
        } catch (InvalidLoginException e) {
            throw new ContextFileException("Could not login with provided info", e);
        }
    }

    public TargetAuthority getTestedAuthority() throws ContextFileException {
        String testedAggregateManagerUrnString = prop.getProperty("testedAggregateManagerUrn");
        String testedAggregateManagerUrnToConnectString = prop.getProperty("testedAggregateManagerUrnToConnect");
        String testedAggregateManagerUrnForRspecString = prop.getProperty("testedAggregateManagerUrnForRspec");

        if (testedAggregateManagerUrnString != null) testedAggregateManagerUrnString = testedAggregateManagerUrnString.trim();
        if (testedAggregateManagerUrnToConnectString != null) testedAggregateManagerUrnToConnectString = testedAggregateManagerUrnToConnectString.trim();
        if (testedAggregateManagerUrnForRspecString != null) testedAggregateManagerUrnForRspecString = testedAggregateManagerUrnForRspecString.trim();

        if ((testedAggregateManagerUrnString == null || testedAggregateManagerUrnString.isEmpty()) &&
                (testedAggregateManagerUrnToConnectString == null || testedAggregateManagerUrnToConnectString.isEmpty()))
            throw new ContextFileException("property \"testedAggregateManagerUrn\" or \"testedAggregateManagerUrnToConnect\" is missing");

        if ((testedAggregateManagerUrnString == null || testedAggregateManagerUrnString.isEmpty()) &&
                (testedAggregateManagerUrnForRspecString == null || testedAggregateManagerUrnForRspecString.isEmpty()))
            throw new ContextFileException("property \"testedAggregateManagerUrn\" or \"testedAggregateManagerUrnForRspec\" is missing");

        final GeniUrn testedAggregateManagerUrn;
        final GeniUrn testedAggregateManagerUrnToConnect;
        final GeniUrn testedAggregateManagerUrnForRspec;
        try {
            if (testedAggregateManagerUrnString != null)
                testedAggregateManagerUrn = new GeniUrn(testedAggregateManagerUrnString);
            else
                testedAggregateManagerUrn = null;
        } catch (GeniUrn.GeniUrnParseException e) {
            throw new ContextFileException("testedAggregateManagerUrn is not valid: \""+testedAggregateManagerUrnString+"\"", e);
        }
        try {
            if (testedAggregateManagerUrnToConnectString != null)
                testedAggregateManagerUrnToConnect = new GeniUrn(testedAggregateManagerUrnToConnectString);
            else
                testedAggregateManagerUrnToConnect = null;
        } catch (GeniUrn.GeniUrnParseException e) {
            throw new ContextFileException("testedAggregateManagerToConnectUrn is not valid: \""+testedAggregateManagerUrnToConnectString+"\"", e);
        }
        try {
            if (testedAggregateManagerUrnForRspecString != null)
                testedAggregateManagerUrnForRspec = new GeniUrn(testedAggregateManagerUrnForRspecString);
            else
                testedAggregateManagerUrnForRspec = null;
        } catch (GeniUrn.GeniUrnParseException e) {
            throw new ContextFileException("testedAggregateManagerForRspecUrn is not valid: \""+testedAggregateManagerUrnForRspecString+"\"", e);
        }

        if (testedAggregateManagerUrnToConnect != null && testedAggregateManagerUrnForRspec != null && testedAggregateManagerUrn != null)
            LOG.warn("having 3 testedAggregateManagerUrn options in the context file makes no sense. testedAggregateManagerUrn will be ignored.");

        Server sfaAuthorityToConnect =
                testedAggregateManagerUrnToConnect != null ?
                        testbedInfoSource.getByUrnExact(testedAggregateManagerUrnToConnect) :
                        authorityFinder.findByUrn(testedAggregateManagerUrn, AuthorityFinder.Purpose.CREATE_SLIVER);

        Server sfaAuthorityForRspec =
                testedAggregateManagerUrnForRspec != null ?
                        testbedInfoSource.getByUrnExact(testedAggregateManagerUrnForRspec) :
                        authorityFinder.findByUrn(testedAggregateManagerUrn, AuthorityFinder.Purpose.REQUEST_RSPEC);

        if (sfaAuthorityToConnect == null && testedAggregateManagerUrnToConnect != null) {
            throw new ContextFileException("testedAggregateManagerUrnToConnect \""+testedAggregateManagerUrnToConnectString+"\" is not a known authority");
        }
        if (sfaAuthorityToConnect == null && testedAggregateManagerUrnToConnect == null) {
            throw new ContextFileException("testedAggregateManagerUrn \""+testedAggregateManagerUrnString+"\" is not a known authority");
        }

        if (sfaAuthorityForRspec == null && testedAggregateManagerUrnForRspec != null) {
            throw new ContextFileException("testedAggregateManagerUrnForRspec \""+testedAggregateManagerUrnForRspecString+"\" is not a known authority");
        }
        if (sfaAuthorityForRspec == null && testedAggregateManagerUrnForRspec == null) {
            throw new ContextFileException("testedAggregateManagerUrn \""+testedAggregateManagerUrnString+"\" is not a known authority");
        }

        TargetAuthority res = new TargetAuthority(testedAggregateManagerUrn, sfaAuthorityForRspec, sfaAuthorityToConnect);

        return res;
    }

    public Properties getProperties() {
        return prop;
    }



    public void setTimeoutAndBusyHandlingFromProperties(JFedPreferences jFedPreferences) {
        //TODO: for missing values, do not use default here but don't change them instead

        String timeoutMsBeforeRetry = prop.getProperty("timoutRetryIntervalMs", "5000");
        String timeoutMaxRetries = prop.getProperty("timoutRetryMaxCount", "3");
        String busyMsBeforeRetry = prop.getProperty("busyRetryIntervalMs", "5000");
        String busyMaxRetries = prop.getProperty("busyRetryMaxCount", "24");
        String possibleTemporaryErrorMsBeforeRetry = prop.getProperty("possibleTemporaryErrorMsBeforeRetry", "1000");
        String possibleTemporaryErrorMaxRetries = prop.getProperty("possibleTemporaryErrorMaxRetries", "1");

        jFedPreferences.setRetrySettings(new RetrySettings(
                        Integer.parseInt(timeoutMaxRetries),
                        Long.parseLong(timeoutMsBeforeRetry),
                        Integer.parseInt(busyMaxRetries),
                        Long.parseLong(busyMsBeforeRetry),
                        Integer.parseInt(possibleTemporaryErrorMaxRetries),
                        Long.parseLong(possibleTemporaryErrorMsBeforeRetry))
        );
    }

    public class ContextFileException extends Exception {
        public ContextFileException() {
        }

        public ContextFileException(String message) {
            super(message);
        }

        public ContextFileException(String message, Throwable cause) {
            super(message, cause);
        }

        public ContextFileException(Throwable cause) {
            super(cause);
        }

    }
}
