package incheon.com.security.util;

import incheon.com.cmm.context.RequestContext;
import incheon.com.security.vo.LoginVO;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Spring Security 권한 검증 유틸리티
 * - 시스템 권한 (SYS Scope): 시스템 전체에서 유효
 * - 메뉴 권한 (MENU Scope): 특정 메뉴에서만 유효
 */
public class SecurityUtil {

    /**
     * 권한 체크 (OR 조건)
     * @param system 시스템 코드
     * @param permissions 권한 코드 (하나라도 있으면 true)
     */
    public static boolean hasPermission(String system, String... permissions) {
        LoginVO currentUser = getCurrentUser();
        if (currentUser == null || currentUser.getUserAuthrts() == null) {
            return false;
        }

        // 현재 메뉴 코드 가져오기 (RequestContext)
        String currentMenuCd = RequestContext.getCurrentMenuCd();

        // 권한 체크: 시스템 권한 OR 메뉴 권한
        for (String permission : permissions) {
            boolean hasPermission = currentUser.getUserAuthrts().stream()
                    .anyMatch(authrt ->
                            authrt.getSysCd().equals(system) &&
                            authrt.getAuthrtCd().equals(permission) &&
                            (
                                // 1. 시스템 수준 권한 (모든 메뉴에서 유효)
                                "SYS".equals(authrt.getScope()) ||
                                // 2. 메뉴 수준 권한 (현재 메뉴에서만 유효)
                                ("MENU".equals(authrt.getScope()) &&
                                 currentMenuCd != null &&
                                 currentMenuCd.equals(authrt.getTrgtCd()))
                            )
                    );
            if (hasPermission) {
                return true;
            }
        }
        return false;
    }

    /**
     * 권한 체크 (AND 조건)
     * @param system 시스템 코드
     * @param permissions 권한 코드 (모두 있어야 true)
     */
    public static boolean hasAllPermissions(String system, String... permissions) {
        LoginVO currentUser = getCurrentUser();
        if (currentUser == null || currentUser.getUserAuthrts() == null) {
            return false;
        }

        // 현재 메뉴 코드 가져오기 (RequestContext)
        String currentMenuCd = RequestContext.getCurrentMenuCd();

        // 권한 체크: 시스템 권한 OR 메뉴 권한
        for (String permission : permissions) {
            boolean hasPermission = currentUser.getUserAuthrts().stream()
                    .anyMatch(authrt ->
                            authrt.getSysCd().equals(system) &&
                            authrt.getAuthrtCd().equals(permission) &&
                            (
                                // 1. 시스템 수준 권한 (모든 메뉴에서 유효)
                                "SYS".equals(authrt.getScope()) ||
                                // 2. 메뉴 수준 권한 (현재 메뉴에서만 유효)
                                ("MENU".equals(authrt.getScope()) &&
                                 currentMenuCd != null &&
                                 currentMenuCd.equals(authrt.getTrgtCd()))
                            )
                    );
            if (!hasPermission) {
                return false;
            }
        }
        return true;
    }

    /**
     * 권한 없으면 예외 발생 (OR 조건)
     * @throws AccessDeniedException 권한 없을 때
     */
    public static void requirePermission(String system, String... permissions) throws AccessDeniedException {
        if (!hasPermission(system, permissions)) {
            throw new AccessDeniedException("권한이 없습니다.");
        }
    }

    /**
     * 권한 없으면 예외 발생 (AND 조건)
     * @throws AccessDeniedException 권한 하나라도 없을 때
     */
    public static void requireAllPermissions(String system, String... permissions) throws AccessDeniedException {
        if (!hasAllPermissions(system, permissions)) {
            throw new AccessDeniedException("권한이 없습니다.");
        }
    }

    /**
     * 시스템 접근 가능 여부 확인
     */
    public static boolean canAccessSystem(String systemCode) {
        LoginVO currentUser = getCurrentUser();
        if (currentUser == null) {
            return false;
        }

        List<String> activeSysCds = currentUser.getActiveSysCds();
        return activeSysCds != null && activeSysCds.contains(systemCode);
    }

    /**
     * 메뉴 접근 가능 여부 확인 (PERM_MENU_ACCESS 체크)
     */
    public static boolean canAccessMenu(String system, String menuCd) {
        if (menuCd == null) {
            return true; // 메뉴가 없는 요청 (API 등)은 통과
        }

        LoginVO currentUser = getCurrentUser();
        if (currentUser == null || currentUser.getUserAuthrts() == null) {
            return false;
        }

        // PERM_MENU_ACCESS 권한 체크
        return currentUser.getUserAuthrts().stream()
                .anyMatch(authrt ->
                        authrt.getSysCd().equals(system) &&
                        "PERM_MENU_ACCESS".equals(authrt.getAuthrtCd()) &&
                        (
                            // 1. 시스템 수준 메뉴 접근 권한 (모든 메뉴 접근 가능)
                            "SYS".equals(authrt.getScope()) ||
                            // 2. 메뉴 수준 메뉴 접근 권한 (특정 메뉴만 접근 가능)
                            ("MENU".equals(authrt.getScope()) &&
                             menuCd.equals(authrt.getTrgtCd()))
                        )
                );
    }

    /**
     * 메뉴 접근 권한 없으면 예외 발생
     */
    public static void requireMenuAccess(String system, String menuCd) throws AccessDeniedException {
        if (!canAccessMenu(system, menuCd)) {
            throw new AccessDeniedException("권한이 없습니다.");
        }
    }

    /**
     * 현재 로그인 사용자 정보
     */
    public static LoginVO getCurrentUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated()) {
            return null;
        }

        Object principal = authentication.getPrincipal();
        if (principal instanceof LoginVO) {
            return (LoginVO) principal;
        }

        return null;
    }

    /**
     * 시스템 권한 목록 (SYS scope만)
     */
    public static Set<String> getPermissions(String systemCode) {
        LoginVO currentUser = getCurrentUser();
        if (currentUser == null || currentUser.getUserAuthrts() == null) {
            return Set.of();
        }

        return currentUser.getUserAuthrts().stream()
                .filter(authrt ->
                        authrt.getSysCd().equals(systemCode) &&
                        "SYS".equals(authrt.getScope())
                )
                .map(authrt -> authrt.getAuthrtCd())
                .collect(Collectors.toSet());
    }

    /**
     * 인증 여부 확인
     */
    public static boolean isAuthenticated() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return authentication != null && authentication.isAuthenticated()
                && !"anonymousUser".equals(authentication.getPrincipal());
    }

    /**
     * 메뉴 권한 확인 (MENU scope)
     */
    public static boolean hasMenuPermission(String system, String menuCd, String permission) {
        LoginVO currentUser = getCurrentUser();
        if (currentUser == null || currentUser.getUserAuthrts() == null) {
            return false;
        }

        return currentUser.getUserAuthrts().stream()
                .anyMatch(authrt ->
                        authrt.getSysCd().equals(system) &&
                        authrt.getAuthrtCd().equals(permission) &&
                        "MENU".equals(authrt.getScope()) &&
                        menuCd.equals(authrt.getTrgtCd())
                );
    }

    /**
     * 역할 보유여부 확인 (단일)
     */
    public static boolean hasRole(String sysCd, String roleCd) {
        LoginVO currentUser = getCurrentUser();
        if (currentUser == null || currentUser.getUserAuthrts() == null) {
            return false;
        }

        return currentUser.getUserRoles().stream()
                .anyMatch(role ->
                        role.getSysCd().equals(sysCd) && role.getRoleCd().equals(roleCd))
                || currentUser.getTempUserRoles().stream()
                .anyMatch(role ->
                        role.getSysCd().equals(sysCd) && role.getRoleCd().equals(roleCd));
    }

    /**
     * 역할 보유여부 확인 (OR 조건)
     * @param sysCd 시스템 코드
     * @param roleCds 역할 코드 (하나라도 있으면 true)
     */
    public static boolean hasRole(String sysCd, String... roleCds) {
        LoginVO currentUser = getCurrentUser();
        if (currentUser == null || currentUser.getUserRoles() == null) {
            return false;
        }

        // 역할 체크: 하나라도 있으면 true
        for (String roleCd : roleCds) {
            boolean hasRole = currentUser.getUserRoles().stream()
                    .anyMatch(role ->
                            role.getSysCd().equals(sysCd) && role.getRoleCd().equals(roleCd))
                    || currentUser.getTempUserRoles().stream()
                    .anyMatch(role ->
                            role.getSysCd().equals(sysCd) && role.getRoleCd().equals(roleCd));
            if (hasRole) {
                return true;
            }
        }
        return false;
    }

    /**
     * 역할 보유여부 확인 (AND 조건)
     * @param sysCd 시스템 코드
     * @param roleCds 역할 코드 (모두 있어야 true)
     */
    public static boolean hasAllRoles(String sysCd, String... roleCds) {
        LoginVO currentUser = getCurrentUser();
        if (currentUser == null || currentUser.getUserRoles() == null) {
            return false;
        }

        // 역할 체크: 모두 있어야 true
        for (String roleCd : roleCds) {
            boolean hasRole = currentUser.getUserRoles().stream()
                    .anyMatch(role ->
                            role.getSysCd().equals(sysCd) && role.getRoleCd().equals(roleCd))
                    || currentUser.getTempUserRoles().stream()
                    .anyMatch(role ->
                            role.getSysCd().equals(sysCd) && role.getRoleCd().equals(roleCd));
            if (!hasRole) {
                return false;
            }
        }
        return true;
    }

    /**
     * 역할 없으면 예외 발생 (OR 조건)
     * @throws AccessDeniedException 역할 없을 때
     */
    public static void requireRole(String sysCd, String... roleCds) throws AccessDeniedException {
        if (!hasRole(sysCd, roleCds)) {
            throw new AccessDeniedException("필요한 역할이 없습니다.");
        }
    }

    /**
     * 역할 없으면 예외 발생 (AND 조건)
     * @throws AccessDeniedException 역할 하나라도 없을 때
     */
    public static void requireAllRoles(String sysCd, String... roleCds) throws AccessDeniedException {
        if (!hasAllRoles(sysCd, roleCds)) {
            throw new AccessDeniedException("필요한 역할이 없습니다.");
        }
    }
}
