package com.seer.teach.common.auth.service;

import cn.dev33.satoken.annotation.SaCheckPermission;
import com.seer.teach.common.auth.dto.PermissionDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

@Slf4j
@Component
public class PermissionScannerService {

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private Environment environment;

    public List<PermissionDTO> scanPermissionAnnotations() {
        String contextPath = environment.getProperty("server.servlet.context-path", "");
        String module = environment.getProperty("spring.application.name");
        contextPath = StringUtils.hasText(contextPath) ? contextPath : "";
        Map<String, Object> controllers = applicationContext.getBeansWithAnnotation(RestController.class);
        List<PermissionDTO> permissions = new ArrayList<>();
        for (Object controller : controllers.values()) {
            Class<?> clazz = AopUtils.getTargetClass(controller);
            Optional<PermissionDTO> permission = scanMethodAnnotations(clazz,contextPath,module);
            permission.ifPresent(permissions::add);
        }
        return permissions;
    }

    private Optional<PermissionDTO> scanMethodAnnotations(Class<?> clazz,String contextPath,String module) {
        if(!clazz.isAnnotationPresent(Tag.class)){
            return Optional.empty();
        }
        String tagName = clazz.getAnnotation(Tag.class).name();
        if( !StringUtils.hasText(tagName)){
            return Optional.empty();
        }
        PermissionDTO permission = new PermissionDTO();
        permission.setModule(module);
        permission.setTitle(tagName);
        permission.setClassName(clazz.getName());
        permission.setType(2);
        List<PermissionDTO.ApiItem> apiItems = new ArrayList<>();
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(SaCheckPermission.class)) {
                SaCheckPermission annotation = method.getAnnotation(SaCheckPermission.class);
                if (Objects.isNull(annotation)) {
                    continue;
                }
                for (String perm : annotation.value()) {
                    log.info("permission: {}", perm);
                    if(StringUtils.hasText(perm)){
                        PermissionDTO.ApiItem apiItem = new PermissionDTO.ApiItem();
                        apiItem.setModule(module);
                        apiItem.setTitle(extractInterfaceName(method));
                        apiItem.setAuthorityCode(perm);
                        apiItem.setHttpMethod(getHttpMethod(method));
                        apiItem.setApiPath(extractApiPath(clazz,contextPath, method));
                        apiItem.setClassName(clazz.getName());
                        apiItem.setMethodName(method.getName());
                        apiItem.setType(3);
                        apiItems.add(apiItem);
                    }
                }
            }
        }
        permission.setApiItems(apiItems);
        return Optional.of(permission);
    }

    private String extractApiPath(Class<?> clazz,String contextPath, Method method) {
        String basePath = contextPath;
        RequestMapping annotation = clazz.getAnnotation(RequestMapping.class);
        if (Objects.nonNull(annotation)) {
            basePath += annotation.value()[0];
        }
        String methodMapping = getHttpMethodPath(method);
        log.info("basePath: {}, methodMapping: {}", basePath, methodMapping);
        return basePath + methodMapping;
    }

    private String extractInterfaceName(Method method) {
        Operation annotation = method.getAnnotation(Operation.class);
        if (Objects.nonNull(annotation)) {
            return annotation.summary();
        }
        return null;
    }

    private String getHttpMethod(Method method) {
        if (method.isAnnotationPresent(GetMapping.class)) return "get";
        if (method.isAnnotationPresent(PostMapping.class)) return "post";
        if (method.isAnnotationPresent(PutMapping.class)) return "put";
        if (method.isAnnotationPresent(DeleteMapping.class)) return "delete";
        return "ANY";
    }

    private String getHttpMethodPath(Method method) {
        if (method.isAnnotationPresent(GetMapping.class) && method.getAnnotation(GetMapping.class).value().length > 0) {
            return method.getAnnotation(GetMapping.class).value()[0];
        }
        if (method.isAnnotationPresent(PostMapping.class) && method.getAnnotation(PostMapping.class).value().length > 0) {
            return method.getAnnotation(PostMapping.class).value()[0];
        }
        if (method.isAnnotationPresent(PutMapping.class) && method.getAnnotation(PutMapping.class).value().length > 0) {
            return method.getAnnotation(PutMapping.class).value()[0];
        }
        if (method.isAnnotationPresent(DeleteMapping.class) && method.getAnnotation(DeleteMapping.class).value().length > 0) {
            return method.getAnnotation(DeleteMapping.class).value()[0];
        }
        if (method.isAnnotationPresent(RequestMapping.class) && method.getAnnotation(RequestMapping.class).value().length > 0) {
            return method.getAnnotation(RequestMapping.class).value()[0];
        }
        return "";
    }
}