package com.writeoff.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Component
public class PermissionMetadataGuard implements SmartInitializingSingleton {
    private static final Logger log = LoggerFactory.getLogger(PermissionMetadataGuard.class);

    private final RequestMappingHandlerMapping handlerMapping;

    @Value("${writeoff.permission-metadata.strict:false}")
    private boolean strictMode;

    public PermissionMetadataGuard(RequestMappingHandlerMapping handlerMapping) {
        this.handlerMapping = handlerMapping;
    }

    @Override
    public void afterSingletonsInstantiated() {
        List<String> violations = new ArrayList<>();
        Map<RequestMappingInfo, HandlerMethod> mapping = handlerMapping.getHandlerMethods();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : mapping.entrySet()) {
            HandlerMethod handlerMethod = entry.getValue();
            if (!isApiHandler(entry.getKey(), handlerMethod)) {
                continue;
            }
            RequirePermission requirePermission = handlerMethod.getMethodAnnotation(RequirePermission.class);
            if (requirePermission == null) {
                violations.add(describe(entry.getKey(), handlerMethod) + " missing @RequirePermission");
                continue;
            }
            if (isBlank(requirePermission.value())) {
                violations.add(describe(entry.getKey(), handlerMethod) + " missing permission code");
            }
            if (isBlank(requirePermission.auditAction())) {
                violations.add(describe(entry.getKey(), handlerMethod) + " missing auditAction");
            }
            if (requirePermission.dataScope() == null) {
                violations.add(describe(entry.getKey(), handlerMethod) + " missing dataScope");
            }
        }
        if (violations.isEmpty()) {
            log.info("Permission metadata guard passed: all API handlers are compliant.");
            return;
        }
        String message = "Permission metadata guard found " + violations.size() + " violation(s): " + String.join("; ", violations);
        if (strictMode) {
            throw new IllegalStateException(message);
        }
        log.warn(message);
    }

    private boolean isApiHandler(RequestMappingInfo info, HandlerMethod method) {
        String packageName = method.getBeanType().getPackage().getName();
        if (!packageName.startsWith("com.writeoff.module")) {
            return false;
        }
        Set<String> patterns = info.getPatternsCondition() == null ? java.util.Collections.emptySet() : info.getPatternsCondition().getPatterns();
        for (String pattern : patterns) {
            if (!pattern.startsWith("/api/")) {
                continue;
            }
            if (pattern.startsWith("/api/auth/login")
                    || pattern.startsWith("/api/auth/platform-login")
                    || pattern.startsWith("/api/auth/password-public-key")
                    || pattern.startsWith("/api/system/health")
                    || pattern.startsWith("/api/health")
                    || pattern.startsWith("/api/platform/menus/current")) {
                continue;
            }
            return true;
        }
        return false;
    }

    private String describe(RequestMappingInfo info, HandlerMethod method) {
        Set<String> patterns = info.getPatternsCondition() == null ? java.util.Collections.emptySet() : info.getPatternsCondition().getPatterns();
        return method.getBeanType().getSimpleName() + "#" + method.getMethod().getName() + patterns;
    }

    private boolean isBlank(String value) {
        return value == null || value.trim().isEmpty();
    }
}
