package com.samsung.interceptor;

import com.samsung.adminaddress.AdminAddressService;
import com.samsung.authentication.Account;
import com.samsung.common.util.WebUtil;
import com.samsung.core.exception.AuthorizationException;
import com.samsung.core.exception.NoSearchUserException;
import com.samsung.menu.ApiService;
import com.samsung.menu.entity.Api;
import com.samsung.role.RoleService;
import com.samsung.user.entity.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.util.UriTemplate;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Log4j2
public class AuthorizationInterceptor implements HandlerInterceptor {

    @Autowired
    private RoleService roleService;

    @Autowired
    private ApiService apiService;

    @Autowired
    private AdminAddressService adminAddressService;

    @Autowired
    private WebUtil webUtil;
    
    @Value("${admin.address.check:false}")
    private boolean adminAddressCheck;

    @SuppressWarnings("squid:S3516")
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

        User user = Account.currentUser();

        if (user == null) {
            throw new NoSearchUserException("No Search User");
        }

        if (roleService.isSystemAdmin(user.getUserId())) {
        	if(adminAddressCheck) {
        		List<String> adminAddresses = adminAddressService.getAdminAddresses(null);
        		String remoteAddr = webUtil.getClientIp(request);
        		if(ObjectUtils.isNotEmpty(adminAddresses) && !adminAddresses.contains(remoteAddr)) {
        			throw new AuthorizationException("Remote address is not admin ip address.");
        		}
        	}
        	return true;
        }

        StringBuilder path = new StringBuilder(request.getRequestURI().substring(request.getContextPath().length()));
        if (StringUtils.isNotEmpty(request.getQueryString())) path.append("?").append(request.getQueryString());

        String menuId = request.getHeader("menu-id");
        List<Api> authMenuList;

        if (StringUtils.isNotEmpty(menuId)) {
            authMenuList = apiService.getUserAuthApiListByUserIdAndMenuId(user.getUserId(), menuId);
        } else {
            authMenuList = apiService.getUserAuthApiListByUserId(user.getUserId());
        }

        String httpMethod = request.getMethod();
        String decodedUrl = webUtil.getDecodedRequestUrl(path.toString());

        String queryString = "";
        if (decodedUrl.indexOf('?') > -1) {
            String[] uri = decodedUrl.split("\\?");
            decodedUrl = uri[0];
            queryString = uri[1];
        }

        for (Api api : authMenuList) {
            if (api.getHttpMethod().equals(httpMethod) && checkUriMatch(api.getApiPath(), api.getApiParameters(), decodedUrl, queryString)) {
                return true;
            }
        }
        throw new AuthorizationException("API 권한 없음");
    }

    private Map<String, String> getRequestParam(String  query) {
        if (StringUtils.isNotEmpty(query)) {
            query = URLDecoder.decode(query, StandardCharsets.UTF_8);
            return getParamToMap(query);
        } else {
            return new HashMap<>();
        }
    }

    private Map<String, String> getParamToMap(String query) {
        return Arrays.stream(query.split("&"))
                .map(s -> s.split("=", 2))
                .filter(arr -> arr.length == 2)
                .collect(Collectors.toMap(arr -> arr[0].trim(), arr -> arr[1].trim()));
    }

    public boolean checkUriMatch(String apiPath, String apiParameters, String decodedUrl, String queryString){
        Map<String, String> requiredParamMap = new HashMap<>();
        if( StringUtils.isNotEmpty(apiParameters)){
            requiredParamMap = getParamToMap(apiParameters);
        }

        UriTemplate uriTemplate = new UriTemplate(apiPath);
        if ( uriTemplate.matches(decodedUrl) ){
            if(requiredParamMap.isEmpty()) return true;
            for (Map.Entry<String, String> entry : requiredParamMap.entrySet()) {
                log.debug("Required Param : " + entry.getKey() + "  Value : " + entry.getValue());
                Map<String, String> requestParamMap = getRequestParam(queryString);
                if( !requestParamMap.containsKey(entry.getKey()) || !(entry.getValue()).equals(requestParamMap.get(entry.getKey()))){
                    return false;
                }
            }
            return true;
        }else{
            return false;
        }
    }
}
