package cn.skcks.matrix.v2.services.route;

import cn.skcks.matrix.v2.model.route.dto.RouteInfo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;

import javax.annotation.Nullable;
import java.util.*;

@Slf4j
@Service
@AllArgsConstructor
public class RouteServiceImpl implements RouteService {
    private WebApplicationContext applicationContext;

    public List<RouteInfo> getRouteInfoList(RequestMappingInfo info, HandlerMethod method) {
        ArrayList<RouteInfo> routes = new ArrayList<>();
        PathPatternsRequestCondition pathPatternsRequestCondition = info.getPathPatternsCondition();
        if (pathPatternsRequestCondition == null) {
            return routes;
        }

        Set<PathPattern> pathPatterns = pathPatternsRequestCondition.getPatterns();
        String uri = pathPatterns.size() > 0 ? pathPatterns.stream().toList().get(0).getPatternString() : "";

        RequestMethodsRequestCondition methodsCondition = info.getMethodsCondition();
        if (methodsCondition.getMethods().size() == 0) {
            for (HttpMethod httpMethod : HttpMethod.values()) {
                RouteInfo route = RouteInfo.builder()
                        .controller(method.getMethod().getDeclaringClass().getName())
                        .methodName(method.getMethod().getName())
                        .requestUrl(uri)
                        .requestMethod(httpMethod.name())
                        .build();
                routes.add(route);
            }
        } else {
            for (RequestMethod requestMethod : methodsCondition.getMethods()) {
                RouteInfo route = RouteInfo.builder()
                        .controller(method.getMethod().getDeclaringClass().getName())
                        .methodName(method.getMethod().getName())
                        .requestUrl(uri)
                        .requestMethod(requestMethod.name())
                        .build();
                routes.add(route);
            }
        }

        return routes;
    }

    @Nullable
    public RouteInfo getRouteInfo(HttpServletRequest request) {
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
            RequestMappingInfo info = entry.getKey();
            HandlerMethod method = entry.getValue();

            // 从请求获取匹配的 RequestMappingInfo
            RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
            if (matchingInfo != null) {
                List<RouteInfo> routeInfos = getRouteInfoList(matchingInfo, method);
                return routeInfos.size() > 0 ? routeInfos.get(0) : null;
            }
        }

        return null;
    }

    @Override
    public List<RouteInfo> getRoutes() {
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        Set<RouteInfo> list = new HashSet<>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
            RequestMappingInfo info = entry.getKey();
            HandlerMethod method = entry.getValue();
            list.addAll(getRouteInfoList(info, method));
        }
        return list.parallelStream().toList();
    }
}
