package gc.mda.kcg.permission;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * 트리 기반 RBAC 권한 해석기 (wing 프로젝트의 permResolver.ts Java 이식).
 *
 * 핵심 규칙:
 * 1. READ가 게이팅 오퍼레이션: 부모의 READ가 N(deny)이면 자식의 모든 작업도 강제 deny
 * 2. 명시 권한 우선: AUTH_PERM에 grant_yn 명시값이 있으면 그것 사용
 * 3. 상속: 명시값 없으면 부모의 동일 작업 권한을 상속
 * 4. 미정 = 거부 (기본값)
 * 5. 다중 역할: 각 역할의 결과를 OR(합집합)
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class PermResolver {

    public static final List<String> OPERATIONS = List.of("READ", "CREATE", "UPDATE", "DELETE", "EXPORT");

    /**
     * 권한 키 생성 헬퍼.
     */
    public static String makePermKey(String rsrcCd, String operCd) {
        return rsrcCd + "::" + operCd;
    }

    /**
     * 단일 역할의 명시 권한 + 트리 → 해석된 (rsrcCd → operCd[]) 맵.
     *
     * @param treeNodes 전체 트리 노드 (use_yn=Y)
     * @param explicitPerms 해당 역할의 명시 권한 키 → grantYn ('Y'/'N')
     * @return 트리 순회 결과 (모든 사용 가능한 노드에 대한 R/C/U/D/E 결정)
     */
    public Map<String, Set<String>> resolveSingleRole(
            List<PermTree> treeNodes,
            Map<String, String> explicitPerms
    ) {
        // 트리 인덱싱 (parent → children)
        Map<String, List<PermTree>> childrenMap = new HashMap<>();
        Map<String, PermTree> nodeMap = new HashMap<>();
        for (PermTree node : treeNodes) {
            if (!"Y".equals(node.getUseYn())) continue;
            nodeMap.put(node.getRsrcCd(), node);
            String parent = node.getParentCd();
            childrenMap.computeIfAbsent(parent, k -> new ArrayList<>()).add(node);
        }

        // 결과 맵: rsrcCd → granted operations Set
        Map<String, Set<String>> resolved = new HashMap<>();

        // 루트 노드부터 BFS (parentCd가 null인 노드)
        List<PermTree> roots = childrenMap.getOrDefault(null, Collections.emptyList());
        for (PermTree root : roots) {
            walkTree(root, null, childrenMap, explicitPerms, resolved);
        }

        return resolved;
    }

    /**
     * 트리 순회: 부모의 효과적 권한을 컨텍스트로 받아 자식에 전파.
     */
    private void walkTree(
            PermTree node,
            Set<String> parentEffective,
            Map<String, List<PermTree>> childrenMap,
            Map<String, String> explicitPerms,
            Map<String, Set<String>> resolved
    ) {
        Set<String> nodeEffective = new HashSet<>();

        // 1. 각 오퍼레이션에 대해 (READ 먼저, 다른 작업은 그 다음)
        // READ 결정
        boolean readGranted = resolveOperation(node.getRsrcCd(), "READ",
                parentEffective != null && parentEffective.contains("READ"),
                explicitPerms);

        // 부모 READ가 deny면 모든 작업 강제 deny
        boolean parentReadDenied = parentEffective != null && !parentEffective.contains("READ") && parentEffective.contains("__defined__");

        if (readGranted && !parentReadDenied) {
            nodeEffective.add("READ");
        }

        // 다른 작업: READ가 부여된 경우에만 평가
        if (nodeEffective.contains("READ")) {
            for (String op : List.of("CREATE", "UPDATE", "DELETE", "EXPORT")) {
                boolean parentHasOp = parentEffective != null && parentEffective.contains(op);
                boolean granted = resolveOperation(node.getRsrcCd(), op, parentHasOp, explicitPerms);
                if (granted) {
                    nodeEffective.add(op);
                }
            }
        }

        // 마커: 이 노드가 평가되었음을 표시 (자식에서 parent_read_denied 판단용)
        nodeEffective.add("__defined__");

        // 결과 저장 (마커 제외)
        Set<String> publicOps = new HashSet<>(nodeEffective);
        publicOps.remove("__defined__");
        if (!publicOps.isEmpty()) {
            resolved.put(node.getRsrcCd(), publicOps);
        }

        // 자식 재귀
        List<PermTree> children = childrenMap.getOrDefault(node.getRsrcCd(), Collections.emptyList());
        for (PermTree child : children) {
            walkTree(child, nodeEffective, childrenMap, explicitPerms, resolved);
        }
    }

    /**
     * 단일 (rsrc, oper) 권한 해석:
     *   - 명시값이 있으면 그것 우선
     *   - 없으면 부모 권한 상속
     */
    private boolean resolveOperation(String rsrcCd, String operCd, boolean parentGranted, Map<String, String> explicitPerms) {
        String key = makePermKey(rsrcCd, operCd);
        String explicit = explicitPerms.get(key);
        if ("Y".equals(explicit)) return true;
        if ("N".equals(explicit)) return false;
        return parentGranted;
    }

    /**
     * 다중 역할 해석: 각 역할 결과를 OR 합집합.
     *
     * @param treeNodes 전체 트리
     * @param permsByRole 역할 sn → 명시 권한 키 → grantYn 맵
     * @return 최종 (rsrcCd → operCd[]) 맵
     */
    public Map<String, List<String>> resolveMultiRole(
            List<PermTree> treeNodes,
            Map<Long, Map<String, String>> permsByRole
    ) {
        Map<String, Set<String>> merged = new HashMap<>();

        for (Map.Entry<Long, Map<String, String>> entry : permsByRole.entrySet()) {
            Map<String, Set<String>> single = resolveSingleRole(treeNodes, entry.getValue());
            for (Map.Entry<String, Set<String>> e : single.entrySet()) {
                merged.computeIfAbsent(e.getKey(), k -> new HashSet<>()).addAll(e.getValue());
            }
        }

        // Set → List 변환 + 안정적 정렬
        Map<String, List<String>> result = new HashMap<>();
        for (Map.Entry<String, Set<String>> e : merged.entrySet()) {
            List<String> sorted = new ArrayList<>(e.getValue());
            sorted.sort(Comparator.comparingInt(OPERATIONS::indexOf));
            result.put(e.getKey(), sorted);
        }
        return result;
    }

    /**
     * 단일 권한 체크 헬퍼: hasPermission(resolved, "detection:gear-detection", "READ")
     * 부모 fallback 지원: "detection:gear-detection" 미존재 시 "detection" 검사
     */
    public boolean hasPermission(Map<String, List<String>> resolved, String rsrcCd, String operCd) {
        List<String> ops = resolved.get(rsrcCd);
        if (ops != null && ops.contains(operCd)) return true;

        // 부모 fallback
        int colonIdx = rsrcCd.indexOf(':');
        if (colonIdx > 0) {
            String parent = rsrcCd.substring(0, colonIdx);
            List<String> parentOps = resolved.get(parent);
            return parentOps != null && parentOps.contains(operCd);
        }
        return false;
    }
}
