package de.rwth.idsg.bikeman.ixsi;

import com.google.common.base.Strings;
import de.rwth.idsg.bikeman.config.IxsiConfiguration;
import de.rwth.idsg.bikeman.ixsi.repository.SystemValidator;
import de.rwth.idsg.bikeman.web.rest.exception.DatabaseException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import javax.servlet.http.HttpServletRequest;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * Created by max on 08/09/14.
 */
@Slf4j
@RequiredArgsConstructor
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {
    private final SystemValidator systemValidator;

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                   WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        String systemId = getSystemID(request);
        if (systemId == null) {
            throw new DatabaseException("This ip address is not allowed for WebSocket communication");
        } else {
            // to be be used in future
            attributes.put(IxsiConfiguration.SYSTEM_ID_KEY, systemId);
            return super.beforeHandshake(request, response, wsHandler, attributes);
        }
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                               WebSocketHandler wsHandler, Exception ex) {
        super.afterHandshake(request, response, wsHandler, ex);
        log.trace("Handshake complete with {}", request.getRemoteAddress());
    }

    private String getSystemID(ServerHttpRequest request) {
        List<String> ipAddressList = getPossibleIpAddresses(request);
        log.info("ipAddressList for this request: {}", ipAddressList);

        for (String ip : ipAddressList) {
            try {
                return systemValidator.getSystemID(ip);
            } catch (DatabaseException e) {
                // not in db, continue with the next in the list
            }
        }
        return null;
    }

    private static List<String> getPossibleIpAddresses(ServerHttpRequest request) {
        List<String> ipAddressList = new ArrayList<>();

        getFromProxy(request).stream()
                             .filter(s -> !Strings.isNullOrEmpty(s))
                             .forEach(ipAddressList::add);

        ipAddressList.add(getWithInstanceOf(request));
        ipAddressList.add(getFromRemote(request));
        ipAddressList.add(getFromContext());

        return ipAddressList;
    }

    private static List<String> getFromProxy(ServerHttpRequest request) {
        List<String> strings = request.getHeaders().get("X-FORWARDED-FOR");
        if (strings == null) {
            return Collections.emptyList();
        } else {
            return strings;
        }
    }

    private static String getWithInstanceOf(ServerHttpRequest request) {
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            String ipAddress = servletRequest.getServletRequest().getHeader("X-FORWARDED-FOR");
            if (ipAddress == null) {
                ipAddress = servletRequest.getServletRequest().getRemoteAddr();
            }
            return ipAddress;
        }
        return null;
    }

    private static String getFromRemote(ServerHttpRequest request) {
        InetSocketAddress inetSocketAddress = request.getRemoteAddress();
        return inetSocketAddress.getAddress().getHostAddress();
    }

    private static String getFromContext() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        return request.getRemoteAddr();
    }
}

@@ -887,25 +887,27 @@
 va.util.
-ArrayList
+Collections
 ;%0Aimport
@@ -913,35 +913,31 @@
 t java.util.
-Collections
+HashSet
 ;%0Aimport jav
@@ -970,16 +970,38 @@
 til.Map;
+%0Aimport java.util.Set;
 %0A%0A/**%0A *
@@ -2207,35 +2207,34 @@
 uest) %7B%0A        
-Lis
+Se
 t%3CString%3E ipAddr
@@ -2236,20 +2236,18 @@
 pAddress
-List
+es
  = getPo
@@ -2301,20 +2301,18 @@
 pAddress
-List
+es
  for thi
@@ -2336,20 +2336,18 @@
 pAddress
-List
+es
 );%0A%0A    
@@ -2376,20 +2376,18 @@
 pAddress
-List
+es
 ) %7B%0A    
@@ -2621,34 +2621,323 @@
  %7D%0A%0A    
-private static Lis
+// -------------------------------------------------------------------------%0A    // Since Spring uses many abstractions for different APIs, we try every%0A    // possible extraction method there available.%0A    // -------------------------------------------------------------------------%0A%0A    private static Se
 t%3CString
@@ -2990,35 +2990,34 @@
 uest) %7B%0A        
-Lis
+Se
 t%3CString%3E ipAddr
@@ -3034,16 +3034,14 @@
 new 
-ArrayLis
+HashSe
 t%3C%3E(
@@ -3420,19 +3420,18 @@
  static 
-Lis
+Se
 t%3CString
@@ -3617,19 +3617,18 @@
 ns.empty
-Lis
+Se
 t();%0A   
@@ -3660,23 +3660,38 @@
  return 
+new HashSet%3C%3E(
 strings
+)
 ;%0A      

improve HandshakeInterceptor impl