package hudson.plugins.ec2;

import hudson.Extension;
import hudson.plugins.ec2.one.OneEC2Cloud;
import hudson.plugins.ec2.one.OneSlaveTemplate;
import hudson.util.FormValidation;
import hudson.util.IOException2;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.List;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.ServletException;

import org.dom4j.Document;
import org.dom4j.Element;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;

/**
 * The original implementation of {@link EC2Cloud}.
 * 
 * @author Kohsuke Kawaguchi
 */
public class OpenNebula extends OneEC2Cloud {
    private transient Metadata metadata;

    // Used when running unit tests
    public static boolean testMode;

    public final URL url;

    @DataBoundConstructor
    public OpenNebula(final String url, final String accessId,
            final String secretKey, final String privateKey,
            final String instanceCapStr,
            final List<OneSlaveTemplate> templates)
            throws MalformedURLException {
        this(new URL(url), accessId, secretKey, privateKey,
            instanceCapStr, templates);
    }

    @DataBoundConstructor
    public OpenNebula(final URL url, final String accessId,
            final String secretKey, final String privateKey,
            final String instanceCapStr,
            final List<OneSlaveTemplate> templates) {
        super("one-", accessId, secretKey, privateKey, instanceCapStr,
            templates);
        this.url = url;
    }

    private Metadata getMetadata() throws IOException {
        if (metadata == null) {
            metadata = new Metadata(url);
        }
        return metadata;
    }

    @Override
    public URL getEc2EndpointUrl() throws IOException {
        return getMetadata().ec2endpoint;
    }

    @Override
    public URL getS3EndpointUrl() throws IOException {
        return getMetadata().s3endpoint;
    }

    @Extension
    public static class DescriptorImpl extends OneEC2Cloud.DescriptorImpl {
        @Override
        public String getDisplayName() {
            return "OpenNebula";
        }

        @Override
        public FormValidation doTestConnection(
                @QueryParameter final URL url,
                @QueryParameter final String accessId,
                @QueryParameter final String secretKey,
                @QueryParameter final String privateKey)
                throws IOException, ServletException {
            return super.doTestConnection(new Metadata(url).ec2endpoint,
                accessId, secretKey, privateKey);
        }
    }

    /**
     * OpenNebula service endpoint metadata.
     */
    static class Metadata {
        final URL ec2endpoint, s3endpoint;

        Metadata(final URL oneUrl) throws IOException {

            // InputStream is = readMetadata(oneUrl);

            ec2endpoint = getUrl(oneUrl);
            s3endpoint = getUrl(oneUrl);
        }

        /**
         * @param oneUrl
         * @return
         * @throws MalformedURLException
         */
        private URL getUrl(final URL oneUrl) throws MalformedURLException {
            return (null != oneUrl) ? oneUrl : new URL(
                "http://opennebula:4568");
        }

        /**
         * @param oneUrl
         * @param metadataUrl
         * @throws IOException
         * @throws NoSuchAlgorithmException
         * @throws KeyManagementException
         */
        private InputStream readMetadata(final URL url)
                throws IOException, NoSuchAlgorithmException,
                KeyManagementException {
            InputStream is = null;
            URL metadataUrl = new URL(getUrl(url), "/");

            try {
                if (getUrl(url).getProtocol().equals("https")) {
                    HttpsURLConnection con =
                        (HttpsURLConnection) getUrl(metadataUrl)
                            .openConnection();
                    makeIgnoreCertificate(con);
                    is = con.getInputStream();
                } else {
                    URLConnection con =
                        getUrl(metadataUrl).openConnection();
                    is = con.getInputStream();
                }
            } catch (IOException e) {
                throw new IOException2(
                    "Failed to parse OpenNebula metadata at "
                        + getUrl(metadataUrl), e);
            } catch (GeneralSecurityException e) {
                throw new IOException2(
                    "Failed to parse OpenNebula metadata at "
                        + getUrl(metadataUrl), e);
            }

            return is;
        }

        /**
         * Configures the given {@link HttpsURLConnection} so that it'll ignore
         * all the HTTPS certificate checks, as typical Eucalyptus
         * implementation doesn't come with a valid certificate.
         */
        private void makeIgnoreCertificate(final HttpsURLConnection con)
                throws NoSuchAlgorithmException, KeyManagementException {
            SSLContext sc = SSLContext.getInstance("SSL");
            TrustManager[] tma = {new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(
                        final X509Certificate[] certs,
                        final String authType) {
                }

                public void checkServerTrusted(
                        final X509Certificate[] certs,
                        final String authType) {
                }
            } };
            sc.init(null, tma, null);

            con.setSSLSocketFactory(sc.getSocketFactory());
            con.setHostnameVerifier(new HostnameVerifier() {
                public boolean verify(final String s,
                        final SSLSession sslSession) {
                    return true; // everything goes
                }
            });
        }

        @Deprecated
        private URL readURLFromMetadata(final Document metadata,
                final String serviceName) throws MalformedURLException {
            Element e =
                (Element) metadata
                    .selectSingleNode("//Service[Name/text()='"
                        + serviceName + "']/EndpointUrl");
            if (e == null) {
                throw new IllegalStateException(
                    "Service metadata didn't contain " + serviceName);
            }
            return new URL(e.getTextTrim());
        }
    }
}
