package vn.azteam.tpf.security.authz;

import org.casbin.jcasbin.main.Enforcer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import vn.azteam.tpf.security.AuthoritiesConstants;
import vn.azteam.tpf.security.apiKey.ApiKeyProvider;
import vn.azteam.tpf.service.TBPermissionQueryService;
import vn.azteam.tpf.service.UserService;
import vn.azteam.tpf.service.dto.TBPermissionDTO;
import vn.azteam.tpf.service.dto.UserDTO;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

import static vn.azteam.tpf.security.contants.Contants.*;

public class AuthzFilter extends GenericFilterBean {

    private static final String tpf_API_PREFIX = "/api/";
    private static final String GET_REQUEST_METHOD = "GET";
    List<String> whiteList = new ArrayList<>(
        Arrays.asList(
            "/api/elasticsearch/index",
            "/api/register",
            "/api/activate",
            "/api/authenticate",
            "/api/account/reset-password/init",
            "/api/account/reset-password/finish",
            "/api/update-fcmToken",
            "/api/delete-fcmToken",
            "/api/notifications/update",
            "/api/notifications/update-list",
            "/api/notifications/update-all",
            "/api/notification/exceed-limit",
            "/api/push-notification",
            "/api/tb-addresses",
            "/api/create-common-activity-guideline-details/",
            "/api/update-common-activity-guideline-details/",
            "/api/delete-common-activity-guideline-details/",
            "/api/tb-customers-api-key",
            "/api/notification/update-person-in-charge"));

    List<String> whiteListWithGetMethod = new ArrayList<>(
        Arrays.asList(
            "/api/users",
            "/api/tb-functions",
            "/api/_search/tb-crops",
            "/api/_search/tb-guidelines",
            "/api/tb-entities/area",
            "/api/tb-entities/net-houses/",
            "/api/tb-entities/customer/",
            "/api/tb-entities-by-customer-id/",
            "/api/tb-customers",
            "/api/listDeviceOfUserCustomers",
            "/api/listParametersOfStatusCrop",
            "/api/getListEnvironmentalParameter",
            "/api/getListTBObjectParametersPestsInvestigation",
            "/api/water-types",
            "/api/tb-supplies/seedling-dropdown-list",
            "/api/tb-supplies-by-type/giathe",
            "/api/tb-guideline-details",
            "/api/list-supplies-types-create",
            "/api/historySupplies/",
            "/api/list-environment-updates-display/",
            "/api/listDeviceForActivity",
            "/api/tb-environmental-parameters",
            "/api/tb-supplies/seed-units",
            "/api/_search/tb-supplies/seed",
            "/api/tb-warehouses",
            "/api/tb-warehouses",
            "/api/list-supplies-in-warehouses/ALL",
            "/api/tb-equipment-types",
            "/api/tb-equipment-types-to-customer",
            "/api/list-supplies-in-warehouses-by-suppliesId/",
            "/api/rules/permissions-by-current-user",
            "/api/tb-equipment-of-customers/",
            "/api/_search/users",
            "/api/listDevicesInEntity",
            "/api/tb-supplies-by-type/GIONG",
            "/api/_search/tb-harvests",
            "/api/usersbyid/",
            "/api/account",
            "/api/unit/capacity",
            "/api/unit/weight",
            "/api/unit/capacity",
            "/api/unit/all",
            "/api/tb-countries",
            "/api/tb-cities",
            "/api/tb-districts",
            "/api/tb-wards",
            "/api/tb-addresses",
            "/api/notifications-current-user",
            "/api/tb-permissions",
            "/api/_search/roles",
            "/api/menus",
            "/api/tb-detail-users",
            "/api/tb-detail-users",
            "/api/locationDevices",
            "/api/tb-equipment-histories",
            "/api/tb-customers-current-user",
            "/api/tb-customers-dropdown-list",
            "/api/tb-roles-dropdown-list",
            "/api/tb-roles-by-customer",
            "/api/listAllActivityTypeOption",
            "/api/listActivityTypeJobInActivityOption",
            "/api/tb-control-types/unit",
            "/api/tb-control-types-option",
            "/api/extend-table-option",
            "/api/tb-crop-types",
            "/api/tb-entity-types",
            "/api/get-all-data-from-table-common",
            "/api/get-data-from-table-common",
            "/api/get-all-units",
            "/api/tb-product-units",
            "/api/list-supplies-in-warehouses",
            "/api/tb-activity-types-dropdown-list",
            "/api/display-object-param-dynamic-form-guideline-details/",
            "/api/display-object-param-dynamic-form",
            "/api/tb-dashboard-types",
            "/api/tb-block-details/options",
            "/api/tb-activity-types-name/",
            "/api/get-all-common-activity-guideline-details/",
            "/api/get-detail-common-activity-guideline-details/",
            "/api/tb-entities/all-net-houses",
            "/api/tb-crops-dropdown-list",
            "/api/activity-environment-update/"));

    List<String> apiKeyListWithGetMethod = new ArrayList<>(
        Arrays.asList(
            "/api/ots/tb-crops-by-customer",
            "/api/ots/tb-crops-detail/",
            "/api/ots/tb-crops/"));

    List<String> apiKeyListNonGetMethod = new ArrayList<>(
        Arrays.asList());

    private final UserService userService;
    private final TBPermissionQueryService tbPermissionQueryService ;
    private final Enforcer enforcer;
    private final ApiKeyProvider apiKeyProvider;

    public AuthzFilter(UserService userService, TBPermissionQueryService tbPermissionQueryService, Enforcer enforcer, ApiKeyProvider apiKeyProvider) {
        this.userService = userService;
        this.tbPermissionQueryService = tbPermissionQueryService;
        this.enforcer = enforcer;
        this.apiKeyProvider = apiKeyProvider;
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String requestPath = request.getRequestURI();
        String requestMethod = request.getMethod();

        // Allows URLs that not starts with the /api/ prefix.
        if(!requestPath.startsWith(tpf_API_PREFIX)){
            filterChain.doFilter(request, response);
            return;
        }

        // URLs are allowed by anyone
        for (String path : whiteList){
            if(requestPath.startsWith(path)){
                filterChain.doFilter(request, response);
                return;
            }
        }

        // URLs are allowed by anyone with GET request
        if(requestMethod.equals(GET_REQUEST_METHOD)){
            for (String path : whiteListWithGetMethod){
                if(requestPath.startsWith(path)){
                    filterChain.doFilter(request, response);
                    return;
                }
            }
        }
        // URLs are allowed by apiKey with GET & NON-GET request
        if(requestPath.startsWith(OTS_API_PREFIX)){
            try {
                if (this.apiKeyHasPermission(request)) {
                    for (String path : apiKeyListWithGetMethod){
                        if(requestPath.startsWith(path)){
                            filterChain.doFilter(request, response);
                            return;
                        }
                    }
                    for (String path : apiKeyListNonGetMethod){
                        if(requestPath.startsWith(path)){
                            filterChain.doFilter(request, response);
                            return;
                        }
                    }
                } else {
                    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                }
            } catch (NoSuchAlgorithmException | InvalidKeyException e) {
                e.printStackTrace();
            }
        }
        if (hasPermission(requestPath, requestMethod, "*")) {
            filterChain.doFilter(request, response);
            return;
        } else {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        }
    }

    /**
     * Get user by login
     * @return
     */
    private Optional<UserDTO> getUserByLogin() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return Optional.empty();
        }

        UserDetails userDetails = (UserDetails) authentication.getPrincipal();

        Optional<UserDTO> userDTO = userService.getUserWithAuthoritiesByLogin(userDetails.getUsername())
            .map(UserDTO::new);

        return userDTO;
    }

    /**
     * Get permission
     * @param requestPath
     * @param requestMethod
     * @param requestType
     * @return
     */
    private boolean hasPermission(String requestPath, String requestMethod, String requestType) {
        Optional<UserDTO> optionalUserDTO = getUserByLogin();
        Long roleId = optionalUserDTO.isPresent() ? optionalUserDTO.get().getRoleId() : Long.MIN_VALUE;
        logger.info("Role: " + roleId);
        logger.info("Request Path: " + requestPath);
        logger.info("Request Method: " + requestMethod);
        logger.info("Request Type: " + requestType);
        boolean result =  enforcer.enforce(roleId.toString(), requestPath, requestMethod, requestType, "true");
        logger.info("Test is Permission Role of current user: " + result);
        return result;
    }
    private boolean apiKeyHasPermission(HttpServletRequest request)
        throws NoSuchAlgorithmException, InvalidKeyException {
        String apiKeyToken = request.getHeader(AUTHORIZATION_API_KEY);
        if (StringUtils.hasText(apiKeyToken)){
            String timestamp = request.getHeader(TIMESTAMP_API_KEY);
            Long customer_id = Long.parseLong(request.getHeader(CUSID_API_KEY));
            return apiKeyProvider.validateToken(customer_id, timestamp, apiKeyToken);
        }
        return false;
    }
}
