package cn.lihongjie.coal.filter;

import cn.lihongjie.coal.common.Constants;
import cn.lihongjie.coal.common.Ctx;
import cn.lihongjie.coal.common.RequestUtils;
import cn.lihongjie.coal.exception.BizException;
import cn.lihongjie.coal.permission.service.PermissionService;
import cn.lihongjie.coal.resource.dto.ResourceDto;
import cn.lihongjie.coal.resource.service.ResourceService;
import cn.lihongjie.coal.role.service.RoleService;
import cn.lihongjie.coal.session.SessionService;
import cn.lihongjie.coal.spring.config.SystemConfig;
import cn.lihongjie.coal.user.dto.UserDto;
import cn.lihongjie.coal.user.service.UserService;

import com.fasterxml.jackson.databind.ObjectMapper;

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

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.PathContainer;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.StopWatch;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.pattern.PathPatternParser;

import java.util.Optional;

@Component
@Order(100)
public class AuthFilter extends OncePerRequestFilter {

    @Autowired SessionService sessionService;

    @Autowired SystemConfig systemConfig;

    @Autowired ResourceService resourceService;

    @Autowired PlatformTransactionManager transactionManager;
    @Autowired ObjectMapper objectMapper;

    @Autowired RoleService roleService;

    @Value("${server.servlet.context-path}")
    private String contextPath;

    @Autowired PermissionService permissionService;

    @Autowired UserService userService;

    @Nullable
    private static Optional<ResourceDto> getResourceDto(
            HttpServletRequest request, HttpServletResponse response) {
        Optional<ResourceDto> resource =
                Optional.ofNullable(
                        (ResourceDto) request.getAttribute(Constants.HTTP_ATTR_RESOURCE));

        if (resource.isEmpty()) {
            RequestUtils.writeResponse(new BizException("invalidUrl", "资源未找到"), response);
            return null;
        }
        return resource;
    }

    private static void doFilter(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
        try {
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
        StopWatch stopWatch = new StopWatch();

        try {

            MDC.remove("user");
            if (isMatches(request)) {

                doFilter(request, response, filterChain);
                return;
            }

            stopWatch.start("get resource");
            String sessionId =
                    StringUtils.defaultIfEmpty(
                            request.getHeader(Constants.HTTP_HEADER_TOKEN),
                            request.getParameter(Constants.HTTP_HEADER_TOKEN));

            Optional<ResourceDto> resource = getResourceDto(request, response);

            stopWatch.stop();

            // 未登录用户访问
            if (StringUtils.isEmpty(sessionId)) {

                stopWatch.start("找到匿名权限");
                // 找到匿名权限
                boolean isAnonymous =
                        permissionService.isAnonymousByResourceId(resource.get().getId());
                stopWatch.stop();
                if (isAnonymous) {

                    sessionService.anonymousSession();
                    UserDto user = Ctx.currentUser();
                    MDC.put("user", user.getUsername());
                    doFilter(request, response, filterChain);

                } else {

                    RequestUtils.writeResponse(new BizException("loginRequired", "请先登录"), response);
                }

                return;
            }

            //
            try {

                stopWatch.start("重建session");
                sessionService.rebuildSession(sessionId);

                stopWatch.stop();

            } catch (BizException ex) {

                RequestUtils.writeResponse(ex, response);

                return;
            }

            var user = Ctx.currentUser();
            MDC.put("user", user.getUsername());

            stopWatch.start("检查权限");

            boolean hasPermission = false;
            if (BooleanUtils.isTrue(user.getSysAdmin())) {
                hasPermission = true;
            } else if (BooleanUtils.isTrue(user.getOrgAdmin())) {
                // 机构管理员

                hasPermission = permissionService.orgAdminHasPermission(resource.get().getId());
            } else {
                // 普通用户默认权限
                hasPermission = permissionService.orgUserHasPermission(resource.get().getId());

                if (!hasPermission) {

                    // 普通用户指定权限
                    hasPermission = userService.hasResource(user.getId(), resource.get().getId());
                }
            }

            stopWatch.stop();
            if (!hasPermission) {

                RequestUtils.writeResponse(
                        new BizException("invalidAccess", "当前资源未授权，请联系机构管理员处理。"), response);
            } else {

                doFilter(request, response, filterChain);
            }
        } catch (Exception e) {

            logger.info("系统异常", e);
            RequestUtils.writeResponse(new BizException("系统异常"), response);
        } finally {
            if (stopWatch.isRunning()) {
                stopWatch.stop();
            }
            logger.debug(stopWatch.prettyPrint());
        }
    }

    private boolean isMatches(HttpServletRequest request) {
        boolean matches = false;
        for (String url : systemConfig.getAnonymous().getUrl()) {

            matches =
                    PathPatternParser.defaultInstance
                            .parse(url)
                            .matches(
                                    PathContainer.parsePath(
                                            RequestUtils.getRequestURI(request, contextPath)));
            if (matches) {
                break;
            }
        }
        return matches;
    }
}
