package com.cf.imes.framework.organ.core.security;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.framework.common.enums.UserTypeEnum;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.framework.common.util.i18n.core.util.I18nUtils;
import com.cf.imes.framework.common.util.servlet.ServletUtils;
import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.framework.organ.config.OrganProperties;
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
import com.cf.imes.framework.organ.core.service.OrganFrameworkService;
import com.cf.imes.framework.web.config.WebProperties;
import com.cf.imes.framework.web.core.filter.ApiRequestFilter;
import com.cf.imes.framework.web.core.handler.GlobalExceptionHandler;
import com.cf.imes.framework.web.core.util.WebFrameworkUtils;
import com.cf.imes.module.system.enums.ErrorCodeConstants;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.Objects;

import static com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants.NO_PERMISSION_TO_VISIT_ORG;
import static com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants.REQUEST_ORGAN_ID_NOT_EXIST;

/**
 * 多组织 Security Web 过滤器
 * 1. 如果是登陆的用户，校验是否有权限访问该组织，避免越权问题。
 * 2. 如果请求未带组织的编号，检查是否是忽略的 URL，否则也不允许访问。
 * 3. 校验组织是合法，例如说被禁用、到期
 * <p>
 * 校验用户访问的组织，是否是其所在的组织，
 *
 * @author 晨丰科技
 */
@Slf4j
public class OrganSecurityWebFilter extends ApiRequestFilter {

    private final OrganProperties organProperties;

    private final AntPathMatcher pathMatcher;

    private final GlobalExceptionHandler globalExceptionHandler;
    private final OrganFrameworkService organFrameworkService;

    private final I18nUtils i18nUtils;

    public OrganSecurityWebFilter(OrganProperties organProperties,
                                  WebProperties webProperties,
                                  GlobalExceptionHandler globalExceptionHandler,
                                  OrganFrameworkService organFrameworkService,
                                  I18nUtils i18nUtils) {
        super(webProperties);
        this.organProperties = organProperties;
        this.pathMatcher = new AntPathMatcher();
        this.globalExceptionHandler = globalExceptionHandler;
        this.organFrameworkService = organFrameworkService;
        this.i18nUtils = i18nUtils;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        Long organId = WebFrameworkUtils.getOrganId(request);
        // 1. 登陆的用户，校验是否有权限访问该组织，避免越权问题。
        Long deptId = null;
        LoginUser user = SecurityFrameworkUtils.getLoginUser();
        if (user != null) {
            deptId = user.getDeptId();
            // 如果获取不到组织编号，则尝试使用登陆用户的组织编号
            if (organId == null) {
                organId = user.getOrganId();
                OrganContextHolder.setOrganId(organId);
                // 如果传递了组织编号，则进行比对组织编号，避免越权问题
            } else if (!Objects.equals(user.getOrganId(), OrganContextHolder.getOrganId())) { // Cloud 特殊逻辑：如果是 RPC 请求，就不校验了。主要考虑，一些场景下，会调用 OrganUtils 去切换组织
                log.error("[doFilterInternal][组织({}) User({}/{}) 越权访问组织({}) URL({}/{})]",
                        user.getOrganId(), user.getId(), user.getUserType(),
                        OrganContextHolder.getOrganId(), request.getRequestURI(), request.getMethod());
                ServletUtils.writeJSON(response, CommonResult.error(NO_PERMISSION_TO_VISIT_ORG.getCode(), i18nUtils.getMessage(NO_PERMISSION_TO_VISIT_ORG.getMsg())));
                return;
            }
        }

        // 如果非允许忽略组织的 URL，则校验组织是否合法
        if (!isIgnoreUrl(request)) {
            // 2. 如果请求未带组织的编号，不允许访问。
            if (organId == null) {
                log.error("[doFilterInternal][URL({}/{}) 未传递组织编号]", request.getRequestURI(), request.getMethod());
                ServletUtils.writeJSON(response, CommonResult.error(REQUEST_ORGAN_ID_NOT_EXIST.getCode(), i18nUtils.getMessage(REQUEST_ORGAN_ID_NOT_EXIST.getMsg())));
                return;
            }
            // 3. 校验组织是合法，例如说被禁用、到期
            try {
                // 校验组织
                organFrameworkService.validOrgan(organId);
                if (ObjectUtil.isNotNull(deptId)) {
                    // 校验部门
                    organFrameworkService.validDept(deptId);
                }

                // 校验组织产品有效性
                validOrgnProduct(user, request);

            } catch (Throwable ex) {
                CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
                // 组织失效返回401踢出系统
                needUnauthorizedWhenOrgExpired(ex, result);
                result.setMsg(i18nUtils.getMessage(result.getMsg(), null));
                ServletUtils.writeJSON(response, result);
                return;
            }
        } else { // 如果是允许忽略组织的 URL，若未传递组织编号，则默认忽略组织编号，避免报错
            if (organId == null) {
                OrganContextHolder.setIgnore(true);
            }
        }

        // 继续过滤
        chain.doFilter(request, response);
    }

    /**
     * 校验组织产品有效性
     *
     * @param user
     * @param request
     */
    private void validOrgnProduct(LoginUser user, HttpServletRequest request) {
        Long productId = user.getProductId();
        boolean isManageEndPoint = Objects.equals(user.getUserType(), UserTypeEnum.ADMIN.getValue());
        // 非管理端校验产品的有效性
        if (!isManageEndPoint) {
            if (ObjectUtil.isNull(productId)) {
                throw new ServiceException(GlobalErrorCodeConstants.USER_PRODUCTID_NOT_EXIST);
            }
            if (!isProductIgnoreUrl(request)) {
                organFrameworkService.validOrgProduct(user.getOrganId(), productId);
            }
        }
    }

    private boolean isIgnoreUrl(HttpServletRequest request) {
        // 快速匹配，保证性能
        if (CollUtil.contains(organProperties.getIgnoreUrls(), request.getRequestURI())) {
            return true;
        }
        // 逐个 Ant 路径匹配
        for (String url : organProperties.getIgnoreUrls()) {
            if (pathMatcher.match(url, request.getRequestURI())) {
                return true;
            }
        }
        return false;
    }

    private boolean isProductIgnoreUrl(HttpServletRequest request) {
        // 快速匹配，保证性能
        if (CollUtil.contains(organProperties.getIgnoreProductUrls(), request.getRequestURI())) {
            return true;
        }
        // 逐个 Ant 路径匹配
        for (String url : organProperties.getIgnoreProductUrls()) {
            if (pathMatcher.match(url, request.getRequestURI())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否组织失效设置未登录回调
     *
     * @param ex
     * @return
     */
    private void needUnauthorizedWhenOrgExpired(Throwable ex, CommonResult<?> result) {
        if (ex instanceof ServiceException) {
            ServiceException serviceException = (ServiceException) ex;
            if (Objects.equals(serviceException.getCode(), ErrorCodeConstants.ORGAN_EXPIRE.getCode())
                    || Objects.equals(serviceException.getCode(), ErrorCodeConstants.ORGAN_DISABLE.getCode())
                    || Objects.equals(serviceException.getCode(), ErrorCodeConstants.ORGAN_NOT_EXISTS.getCode())
                    || Objects.equals(serviceException.getCode(), ErrorCodeConstants.DEPT_DISABLE.getCode())
                    || Objects.equals(serviceException.getCode(), ErrorCodeConstants.DEPT_NOT_FOUND.getCode())
                    || Objects.equals(serviceException.getCode(), ErrorCodeConstants.DEPT_PARENT_NOT_EXITS.getCode())) {
                result.setCode(GlobalErrorCodeConstants.UNAUTHORIZED.getCode());
            }
        }
    }
}
