/*
 * Copyright 2000-2012 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jetbrains.buildServer.commitPublisher.github.api.impl;

import com.intellij.openapi.diagnostic.Logger;
import jetbrains.buildServer.commitPublisher.HttpHelper;
import jetbrains.buildServer.serverSide.TeamCityProperties;
import jetbrains.buildServer.util.StringUtil;
import jetbrains.buildServer.version.ServerVersionHolder;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.NTCredentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.RequestAcceptEncoding;
import org.apache.http.client.protocol.ResponseContentEncoding;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
import org.apache.http.impl.conn.SchemeRegistryFactory;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HttpContext;
import org.jetbrains.annotations.NotNull;

import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
 * Created by Eugene Petrenko (eugene.petrenko@gmail.com)
 * Date: 11.08.11 16:24
 */
public class HttpClientWrapperImpl implements HttpClientWrapper {
  private final Logger LOG = Logger.getInstance(HttpClientWrapperImpl.class.getName());

  private final HttpClient myClient;

  public HttpClientWrapperImpl() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    final HttpParams ps = new BasicHttpParams();

    DefaultHttpClient.setDefaultHttpParams(ps);
    final int timeout = TeamCityProperties.getInteger("teamcity.github.http.timeout", 300 * 1000);
    HttpConnectionParams.setConnectionTimeout(ps, timeout);
    HttpConnectionParams.setSoTimeout(ps, timeout);
    HttpProtocolParams.setUserAgent(ps, HttpHelper.buildUserAgentString());

    final SchemeRegistry schemaRegistry = SchemeRegistryFactory.createDefault();
    final SSLSocketFactory sslSocketFactory = new SSLSocketFactory(new TrustStrategy() {
      public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        return !TeamCityProperties.getBoolean("teamcity.github.verify.ssl.certificate");
      }
    }) {
      @Override
      public Socket connectSocket(int connectTimeout, Socket socket,
              HttpHost host, InetSocketAddress remoteAddress,
              InetSocketAddress localAddress, HttpContext context) throws IOException {
        if (socket instanceof SSLSocket) {
          try {
            PropertyUtils.setProperty(socket, "host", host.getHostName());
          } catch (Exception ex) {
            LOG.warn(String.format("A host name is not passed to SSL connection for the purpose of supporting SNI due to the following exception: %s", ex.toString()));
          }
        }
        return super.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context);
      }
    };
    schemaRegistry.register(new Scheme("https", 443, sslSocketFactory));

    final DefaultHttpClient httpclient = new DefaultHttpClient(new ThreadSafeClientConnManager(schemaRegistry), ps);

    setupProxy(httpclient);

    httpclient.setRoutePlanner(new ProxySelectorRoutePlanner(
            httpclient.getConnectionManager().getSchemeRegistry(),
            ProxySelector.getDefault()));
    httpclient.addRequestInterceptor(new RequestAcceptEncoding());
    httpclient.addResponseInterceptor(new ResponseContentEncoding());
    httpclient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(3, true));
    httpclient.setRedirectStrategy(new LaxRedirectStrategy());

    myClient = httpclient;
  }

  private void setupProxy(DefaultHttpClient httpclient) {
    final String httpProxy = TeamCityProperties.getProperty("teamcity.github.http.proxy.host");
    if (StringUtil.isEmptyOrSpaces(httpProxy)) return;

    final int httpProxyPort = TeamCityProperties.getInteger("teamcity.github.http.proxy.port", -1);
    if (httpProxyPort <= 0) return;

    LOG.info("TeamCity.GitHub will use proxy: " + httpProxy + ", port " + httpProxyPort);
    httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, new HttpHost(httpProxy, httpProxyPort));

    final String httpProxyUser = TeamCityProperties.getProperty("teamcity.github.http.proxy.user");
    final String httpProxyPassword = TeamCityProperties.getProperty("teamcity.github.http.proxy.password");
    final String httpProxyDomain = TeamCityProperties.getProperty("teamcity.github.http.proxy.domain");
    final String httpProxyWorkstation = TeamCityProperties.getProperty("teamcity.github.http.proxy.workstation");

    if (StringUtil.isEmptyOrSpaces(httpProxyUser) || StringUtil.isEmptyOrSpaces(httpProxyPassword)) return;

    final Credentials creds;
    if (StringUtil.isEmptyOrSpaces(httpProxyDomain) || StringUtil.isEmptyOrSpaces(httpProxyWorkstation)) {
      LOG.info("TeamCity.GitHub will use proxy credentials: " + httpProxyUser);
      creds = new UsernamePasswordCredentials(httpProxyUser, httpProxyPassword);
    } else {
      LOG.info("TeamCity.GitHub will use proxy NT credentials: " + httpProxyDomain + "/" + httpProxyUser);
      creds = new NTCredentials(httpProxyUser, httpProxyPassword, httpProxyWorkstation, httpProxyDomain);
    }

    httpclient.getCredentialsProvider().setCredentials(
            new AuthScope(httpProxy, httpProxyPort),
            creds
    );
  }

  @NotNull
  public HttpResponse execute(@NotNull HttpUriRequest request) throws IOException {
    return myClient.execute(request);
  }

  public void dispose() {
    myClient.getConnectionManager().shutdown();
  }

}
