package be.iminds.ilabt.jfed;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Service;
import be.iminds.ilabt.jfed.lowlevel.JFedException;
import be.iminds.ilabt.jfed.lowlevel.TestCertHelper;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.impl.AutomaticAggregateManagerWrapper;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.impl.AutomaticUserAndSliceApiWrapper;
import be.iminds.ilabt.jfed.lowlevel.connection.*;
import be.iminds.ilabt.jfed.util.JFedPasswordManager;
import be.iminds.ilabt.jfed.util.JFedTrustStore;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import org.apache.xmlrpc.XmlRpcRequest;

import javax.inject.Singleton;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.*;

/**
 * Fake connections for testing, with mock replies to requests.
 * <p/>
 * This uses almost every real thing, except for the underlying XmlRpc transport etc.
 */
public class MockHttpRepliesTestCoreModule extends AbstractModule {
    protected final TestCertHelper testCertHelper;
    protected final List<MockSfaConnection.MockXmlRpcReply> mockReplies;


    protected final List<MockSfaConnection.MockXmlRpcHistoryItem> mockXmlRpcHistory =
            Collections.synchronizedList(new ArrayList<MockSfaConnection.MockXmlRpcHistoryItem>());
    protected final List<XmlRpcRequest> noMockXmlRpcRequestInfo =
            Collections.synchronizedList(new ArrayList<XmlRpcRequest>());

    protected final MockSfaConnection.MockXmlRpcHistoryListener mockXmlRpcHistoryListener =
            mockXmlRpcHistory::add;

    protected final MockSfaConnection.OnMockXmlRpcListener onNoMockXmlRpcListener =
            noMockXmlRpcRequestInfo::add;

    public MockHttpRepliesTestCoreModule() {
        this(new TestCertHelper());
    }

    public MockHttpRepliesTestCoreModule(TestCertHelper testCertHelper) {
        this.testCertHelper = testCertHelper;
        this.mockReplies = new ArrayList<>();
    }

    public void addMockReply(MockSfaConnection.MockXmlRpcReply mr) {
        mockReplies.add(mr);
    }


    @Override
    protected void configure() {
        bind(JFedTrustStore.class).in(Singleton.class);
        bind(TestCertHelper.class).toInstance(testCertHelper);
        bind(JFedConnectionProvider.class).to(TestSfaConnectionPool.class).in(Singleton.class);

        bind(AutomaticAggregateManagerWrapper.AutomaticAggregateManagerWrapperFactory.class).
                to(AutomaticAggregateManagerWrapper.BasicAutomaticAggregateManagerWrapperFactory.class).in(Singleton.class);

        bind(AutomaticUserAndSliceApiWrapper.AutomaticUserAndSliceApiWrapperFactory.class).
                to(AutomaticUserAndSliceApiWrapper.BasicAutomaticUserAndSliceApiWrapperFactory.class).in(Singleton.class);
    }

    private Map<String, MockSfaConnection> conCache = Collections.synchronizedMap(new HashMap<String, MockSfaConnection>());

    public MockSfaConnection createCon(TestbedInfoSource testbedInfoSource, String urlStr) throws JFedException {
        if (conCache.containsKey(urlStr) && !conCache.get(urlStr).isError()) return conCache.get(urlStr);

        URL url;
        try {
            url = new URL(urlStr);
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }

        Server server = testbedInfoSource.getServerByUrl(url);
        assert server != null;

        Service service = testbedInfoSource.getServiceByUrl(url);
        assert service != null;

        assert Objects.equals(service.getServer().getId(), server.getId());

        MockSfaConnection con;
        try {
            con = new MockSfaConnection(new URL(urlStr), service, null);
        } catch (MalformedURLException e) {
            throw new RuntimeException("Malformed URL used in test: " + urlStr, e);
        }
        con.setMockReplies(mockReplies);

        con.setNoMockListeners(Collections.singletonList(onNoMockXmlRpcListener));
        con.setHistoryListeners(Collections.singletonList(mockXmlRpcHistoryListener));

        conCache.put(urlStr, con);

        return con;
    }

    @Provides
    public ConnectionBuilderFactory provideConnectionBuilderFactory(final TestbedInfoSource testbedInfoSource) throws JFedException {
        assert testbedInfoSource != null;
        Server userAuth = testbedInfoSource.getFromAnyUrnExact(TestCertHelper.saUrn);
        Server amAuth = testbedInfoSource.getFromAnyUrnExact(TestCertHelper.am1Urn);
        Server am2Auth = testbedInfoSource.getFromAnyUrnExact(TestCertHelper.am2Urn);
        Server scsAuth = testbedInfoSource.getFromAnyUrnExact(TestCertHelper.scsUrn);
        assert userAuth != null;
        assert amAuth != null;
        assert am2Auth != null;
        assert scsAuth != null;

        return new ConnectionBuilderFactory() {
            @Override
            public ConnectionBuilder createConnectionBuilder() {
                return new ConnectionBuilder() {
                    private String serverUrlStr;

                    @Override
                    public void setUrl(String serverUrlString) {
                        this.serverUrlStr = serverUrlString;
                    }

                    @Override
                    public synchronized void setUrl(URL serverUrl) {
                        //ConnectionBuilder should only be used once. Otherwise, a race condition can cause server connections to the wrong mock server.
                        assert serverUrlStr == null;
                        this.serverUrlStr = serverUrl.toExternalForm();
                    }

                    @Override
                    public synchronized SfaConnection buildSfaConnection() throws JFedException {
                        return createCon(testbedInfoSource, serverUrlStr);
                    }

                    @Override
                    public HttpConnection buildHttpConnection() throws JFedException {
                        return null;
                    }

                    @Override
                    public ConnectionConfig getCurrentConnectionConfig(ConnectionConfig.Type type) {
                        return null;
                    }

                    @Override
                    public void set(ConnectionConfig connectionConfig) {

                    }

                    @Override
                    public void setHackAllowAllServerCertificates(boolean hackAllowAllServerCertificates) {

                    }

                    @Override
                    public void setHackOverrideAllowedServerCertificateAlias(List<String> hackOverrideAllowedServerCertificateAlias) {

                    }

                    @Override
                    public void setDebugInfo(JFedConnection.DebugInfo debugInfo) {
                    }

                    @Override
                    public void setProxy(JFedConnection.ProxyInfo proxyInfo, boolean forceSshProxyDns) {
                    }

                    @Override
                    public void useNoAuthentication() {
                    }

                    @Override
                    public void useHttpBasicAuthentication(JFedPasswordManager.LoginInfo login) {
                    }

                    @Override
                    public void useSslClientAuthentication(List<X509Certificate> clientCertChain, PrivateKey privateKey) {
                    }

                    @Override
                    public void useHttp() {
                    }

                    @Override
                    public void useHttps(JFedTrustStore conTrustStore, HandleUntrustedCallback handleUntrustedCallback) {
                    }
                };
            }
        };
    }

    public TestCertHelper getTestCertHelper() {
        return testCertHelper;
    }

    public List<MockSfaConnection.MockXmlRpcHistoryItem> getMockXmlRpcHistory() {
        return mockXmlRpcHistory;
    }

    public List<XmlRpcRequest> getNoMockXmlRpcRequestInfo() {
        return noMockXmlRpcRequestInfo;
    }
}
