package incheon.com.menu.service.impl;

import incheon.com.cmm.context.RequestContext;
import incheon.com.cmm.util.IpAccessUtil;
import incheon.com.menu.mapper.CommonMenuMapper;
import incheon.com.menu.service.CommonMenuService;
import incheon.com.menu.vo.HeaderMenuVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * 공통 메뉴 Service 구현체
 * 사용자별 역할 기반 메뉴 조회 및 캐싱
 */
@Slf4j
@Service("commonMenuService")
@RequiredArgsConstructor
public class CommonMenuServiceImpl extends EgovAbstractServiceImpl implements CommonMenuService {

    private final CommonMenuMapper menuMapper;

    /**
     * 사용자별 헤더 메뉴 조회 (권한 기반 필터링 적용)
     * 캐시 키: userId + sysCd (사용자별 캐싱)
     *
     * 변경사항: SQL에서 SYS_CD 복합키 조인 및 권한 필터링 처리
     * 추가: IP 접근 제어 필터링 (역할에 IP 제한이 설정된 경우)
     */
    @Override
//    @Cacheable(value = "headerMenuByUser", key = "#userId + '_' + #sysCd")
    public List<HeaderMenuVO> getHeaderMenuByUser(String userId, String sysCd) throws Exception {
        log.debug("사용자별 헤더 메뉴 조회 - userId: {}, sysCd: {}", userId, sysCd);

        Map<String, Object> params = new HashMap<>();
        params.put("userId", userId);
        params.put("sysCd", sysCd);

        // 1. DB에서 권한 필터링된 메뉴 조회
        List<Map<String, Object>> flatList = menuMapper.selectHeaderMenuByUser(params);

        // 2. 계층 구조 변환
        List<HeaderMenuVO> menuTree = buildMenuTree(flatList);

        // 3. IP 접근 제어 - 통합관리자 메뉴 제거
        if (shouldBlockSuperAdminMenu(userId, sysCd)) {
            menuTree.removeIf(menu -> SUPER_ADMIN_MENU_CD.equals(menu.getMenuCd()));
        }

        return menuTree;
    }

    /**
     * 역할 코드 기반 메뉴 조회 (유지보수 계정용)
     */
    @Override
    public List<HeaderMenuVO> getHeaderMenuByRole(String sysCd, List<String> roleCds) throws Exception {
        log.debug("역할 코드 기반 헤더 메뉴 조회 (유지보수) - sysCd: {}, roleCds: {}개", sysCd, roleCds.size());

        Map<String, Object> params = new HashMap<>();
        params.put("sysCd", sysCd);
        params.put("roleCds", roleCds);

        List<Map<String, Object>> flatList = menuMapper.selectHeaderMenuByRole(params);

        return buildMenuTree(flatList);
    }

    /** 통합 관리자 메뉴 코드 */
    private static final String SUPER_ADMIN_MENU_CD = "MNUAGS980000";

    /**
     * 통합관리자 메뉴를 IP 제한으로 차단해야 하는지 확인
     */
    private boolean shouldBlockSuperAdminMenu(String userId, String sysCd) throws Exception {
        String currentIp = RequestContext.getRemoteIp();
        if (!StringUtils.hasText(currentIp)) {
            return false;
        }

        Map<String, Object> params = new HashMap<>();
        params.put("userId", userId);
        params.put("sysCd", sysCd);

        String rulNm = menuMapper.selectSuperAdminRulNm(params);
        if (!StringUtils.hasText(rulNm)) {
            return false;
        }

        return !IpAccessUtil.isAllowed(currentIp, rulNm);
    }

    /**
     * Flat 리스트를 계층 구조(Tree)로 변환
     * 1-depth, 2-depth, 3-depth 처리
     */
    private List<HeaderMenuVO> buildMenuTree(List<Map<String, Object>> flatList) {
        Map<String, HeaderMenuVO> menuMap = new HashMap<>();
        List<HeaderMenuVO> rootMenus = new ArrayList<>();

        // 1단계: Map으로 변환 및 VO 생성
        for (Map<String, Object> row : flatList) {
            HeaderMenuVO menu = new HeaderMenuVO();
            menu.setMenuCd((String) row.get("menuCd"));
            menu.setMenuNm((String) row.get("menuNm"));
            menu.setMenuUrlAddr((String) row.get("menuUrlAddr"));
            menu.setIconPathNm((String) row.get("iconPathNm"));
            menu.setUpMenuCd((String) row.get("upMenuCd"));

            // menuSortSeq가 BigDecimal로 올 수 있음
            Object sortSeq = row.get("menuSortSeq");
            if (sortSeq != null) {
                if (sortSeq instanceof Integer) {
                    menu.setMenuSortSeq((Integer) sortSeq);
                } else if (sortSeq instanceof Number) {
                    menu.setMenuSortSeq(((Number) sortSeq).intValue());
                }
            }

            menu.setChildren(new ArrayList<>());
            menuMap.put(menu.getMenuCd(), menu);
        }

        // 2단계: 계층 구조 구성 (1-depth, 2-depth, 3-depth 모두 처리)
        for (Map<String, Object> row : flatList) {
            String menuCd = (String) row.get("menuCd");
            String upMenuCd = (String) row.get("upMenuCd");
            HeaderMenuVO menu = menuMap.get(menuCd);

            if (upMenuCd == null || upMenuCd.isEmpty() || !menuMap.containsKey(upMenuCd)) {
                // 1-depth 메뉴 (부모 없음)
                rootMenus.add(menu);
            } else {
                // 2-depth 또는 3-depth 메뉴 (부모에 추가)
                HeaderMenuVO parent = menuMap.get(upMenuCd);
                if (parent != null) {
                    parent.getChildren().add(menu);
                }
            }
        }

        // 3단계: 재귀적으로 정렬 (menuSortSeq 기준)
        sortMenusRecursively(rootMenus);

        // 4단계: 첫 번째 리프 URL 계산
        for (HeaderMenuVO menu : rootMenus) {
            calculateFirstLeafUrl(menu);
        }

        return rootMenus;
    }

    /**
     * 메뉴를 재귀적으로 정렬
     */
    private void sortMenusRecursively(List<HeaderMenuVO> menus) {
        if (menus == null || menus.isEmpty()) {
            return;
        }

        menus.sort(Comparator.comparing(
            menu -> menu.getMenuSortSeq() != null ? menu.getMenuSortSeq() : Integer.MAX_VALUE
        ));

        for (HeaderMenuVO menu : menus) {
            if (menu.hasChildren()) {
                sortMenusRecursively(menu.getChildren());
            }
        }
    }

    /**
     * 각 메뉴의 첫 번째 리프 URL 계산 (재귀)
     * menuUrlAddr가 없는 상위 메뉴를 위해 하위 트리에서 첫 번째 리프 URL을 찾음
     */
    private void calculateFirstLeafUrl(HeaderMenuVO menu) {
        // 본인이 URL이 있으면 본인 URL 사용
        if (menu.getMenuUrlAddr() != null && !menu.getMenuUrlAddr().isEmpty()) {
            menu.setFirstLeafUrl(menu.getMenuUrlAddr());
        } else if (menu.hasChildren()) {
            // 하위 메뉴에서 첫 번째 리프 URL 찾기
            String firstLeafUrl = findFirstLeafUrl(menu.getChildren());
            menu.setFirstLeafUrl(firstLeafUrl);
        }

        // 하위 메뉴들도 재귀적으로 처리
        if (menu.hasChildren()) {
            for (HeaderMenuVO child : menu.getChildren()) {
                calculateFirstLeafUrl(child);
            }
        }
    }

    /**
     * 메뉴 리스트에서 첫 번째 리프(URL이 있는 메뉴)를 재귀적으로 찾음
     */
    private String findFirstLeafUrl(List<HeaderMenuVO> menus) {
        if (menus == null || menus.isEmpty()) {
            return null;
        }

        for (HeaderMenuVO menu : menus) {
            // 본인이 URL이 있으면 바로 반환
            if (menu.getMenuUrlAddr() != null && !menu.getMenuUrlAddr().isEmpty()) {
                return menu.getMenuUrlAddr();
            }
            // 하위 메뉴에서 재귀적으로 찾기
            if (menu.hasChildren()) {
                String url = findFirstLeafUrl(menu.getChildren());
                if (url != null) {
                    return url;
                }
            }
        }
        return null;
    }
}
