/*******************************************************************************
 * Copyright 2018 572682
 * 
 * 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 us.dot.its.jpo.ode.dds;

import java.net.URL;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.SSLContext;
import javax.ws.rs.core.Response.Status;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import us.dot.its.jpo.ode.wrapper.HttpClientFactory;
import us.dot.its.jpo.ode.wrapper.HttpClientFactory.HttpClient;
import us.dot.its.jpo.ode.wrapper.HttpClientFactory.HttpException;
import us.dot.its.jpo.ode.wrapper.HttpClientFactory.HttpResponse;

public class CASClient {

   public static final String JSESSIONID_KEY = "JSESSIONID";
   private static final Logger logger = LoggerFactory
         .getLogger(CASClient.class);

   private String ddsCasUrl;
   private String ddsCasUsername;
   private String ddsCasPassword;
   private String body = " body: ";
   private String http = "Error closing HTTP client";

   private HttpClientFactory httpClientFactory;
   private String sessionID;

   public String getSessionID() {
      return sessionID;
   }
   
   public String getDdsCasUrl() {
	      return ddsCasUrl;
   }
   
   public String getDdsCasUsername() {
	      return ddsCasUsername;
   }

   public static CASClient configure(SSLContext sslContext,
         String ddsCasUrl,
         String ddsCasUsername,
         String ddsCasPassword
         ) throws CASException {

      CASClient casClient = new CASClient();
      try {

         casClient.ddsCasUrl = ddsCasUrl;
         casClient.ddsCasUsername = ddsCasUsername;
         casClient.ddsCasPassword = ddsCasPassword;
         casClient.httpClientFactory = HttpClientFactory.build(sslContext);
      } catch (Exception e) {
         throw casClient.new CASException(e);
      }
      return casClient;
   }

   public String login(String websocketURL) throws CASException {

      try {
         String ticketGrantingTicket = getTicketGrantingTicket(ddsCasUrl,
               ddsCasUsername,
               ddsCasPassword);
         logger.info("Got ticketGrantingTicket {}", ticketGrantingTicket);

         String httpWebsocketURL = 
               "https" + websocketURL.substring(websocketURL.indexOf(':'));
         String ddsHttpWebSocketUrl = 
               new URL(httpWebsocketURL).toExternalForm();
         String serviceTicket = getServiceTicket(
               ddsCasUrl, 
               ticketGrantingTicket,
               ddsHttpWebSocketUrl);
         logger.info("Got serviceTicket {}", serviceTicket);

         sessionID = getServiceCall(ddsHttpWebSocketUrl, serviceTicket);
         logger.info("Successful CAS login with sessionID {}", sessionID);

         return sessionID;
      } catch (Exception e) {
         throw new CASException(e);
      }
   }

   private String getTicketGrantingTicket(String server, String username,
         String password) throws CASException {

      HttpClient httpClient = httpClientFactory.createHttpClient();
      try {

         ConcurrentHashMap<String, String> params = new ConcurrentHashMap<>();
         params.put("username", username);
         params.put("password", password);
         HttpResponse response = httpClient.post(server, null, params, null);

         Status statusCode = response.getStatusCode();
         String responseBody = response.getBody();

         if (statusCode == Status.CREATED) { // 201
            Matcher matcher = Pattern.compile(".*action=\".*/(.*?)\".*")
                  .matcher(responseBody);
            if (matcher.matches()) {
               return matcher.group(1);
            } else {
               throw new CASException(
                     "CAS getTicketGrantingTicket failed. No ticket found in body: "
                           + responseBody);
            }
         } else {
            throw new CASException(
                  "CAS getTicketGrantingTicket failed. Response code: "
                        + statusCode + body + responseBody);
         }

      } catch (Exception e) {
         throw new CASException(e);
      } finally {
         try {
            httpClient.close();
         } catch (HttpException e) {
            logger.error(http, e);
         }
      }
   }

   private String getServiceTicket(String server, String ticketGrantingTicket,
         String service) throws CASException {

      HttpClient httpClient = httpClientFactory.createHttpClient();

      try {

         HttpResponse response = httpClient.post(
               server + "/" + ticketGrantingTicket, null,
               Collections.singletonMap("service", service), null);

         Status statusCode = response.getStatusCode();
         String responseBody = response.getBody();

         if (statusCode == Status.OK) { // 200
            return responseBody;
         } else {
            throw new CASException(
                  "CAS getServiceTicket failed. Response code: " + statusCode
                        + body + responseBody);
         }

      } catch (HttpException e) {
         throw new CASException(e);
      } finally {
         try {
            httpClient.close();
         } catch (HttpException e) {
            logger.error(http, e);
         }
      }
   }

   private String getServiceCall(String service, String serviceTicket)
         throws CASException {

      HttpClient httpClient = httpClientFactory.createHttpClient();

      try {

         HttpResponse response = httpClient.get(service, null,
               Collections.singletonMap("ticket", serviceTicket));

         Status statusCode = response.getStatusCode();
         String responseBody = response.getBody();

         if (statusCode == Status.OK || statusCode == Status.NOT_FOUND) { // 200
                                                                          // or
                                                                          // 404
            return getSessionID(httpClient.getCookies());
         } else {
            throw new CASException("CAS getServiceCall failed. Response code: "
                  + statusCode + body + responseBody);
         }

      } catch (HttpException e) {
         throw new CASException(e);
      } finally {
         try {
            httpClient.close();
         } catch (HttpException e) {
            logger.error(http, e);
         }
      }
   }

   private String getSessionID(Map<String, String> cookies) {
      sessionID = "";
      for (Map.Entry<String, String> c : cookies.entrySet()) {
         if (c.getKey().equals(JSESSIONID_KEY)) {
            sessionID = c.getValue();
            break;
         }
      }
      return sessionID;
   }

   public class CASException extends Exception {

      private static final long serialVersionUID = 3103235434315019560L;

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

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

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