package gc.mda.kcg.permission;

import gc.mda.kcg.audit.annotation.Auditable;
import gc.mda.kcg.permission.dto.PermissionUpdateRequest;
import gc.mda.kcg.permission.dto.RoleCreateRequest;
import gc.mda.kcg.permission.dto.RoleUpdateRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.UUID;

/**
 * 역할/권한 매트릭스 CRUD 서비스.
 *
 * 권한 변경 시 PermissionService.evictAllPermissions() 호출 → 캐시 무효화.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class RoleManagementService {

    private final RoleRepository roleRepository;
    private final PermRepository permRepository;
    private final PermTreeRepository permTreeRepository;
    private final UserRoleRepository userRoleRepository;
    private final PermissionService permissionService;

    @Auditable(action = "ROLE_CREATE", resourceType = "ROLE")
    @Transactional
    public Role createRole(RoleCreateRequest req) {
        if (roleRepository.findByRoleCd(req.roleCd()).isPresent()) {
            throw new IllegalArgumentException("ROLE_CD_DUPLICATED: " + req.roleCd());
        }
        Role role = Role.builder()
                .roleCd(req.roleCd().toUpperCase())
                .roleNm(req.roleNm())
                .roleDc(req.roleDc())
                .colorHex(req.colorHex())
                .dfltYn("Y".equalsIgnoreCase(req.dfltYn()) ? "Y" : "N")
                .builtinYn("N")
                .build();
        Role saved = roleRepository.save(role);

        // 신규 역할은 최상위 트리 노드의 READ를 N으로 명시 (deny)하여 안전한 기본값 설정
        // 운영자가 PermissionsPanel에서 점진적으로 권한 부여
        List<PermTree> rootNodes = permTreeRepository.findAllByOrderByRsrcLevelAscSortOrdAsc()
                .stream().filter(n -> n.getRsrcLevel() == 0).toList();
        for (PermTree node : rootNodes) {
            permRepository.save(Perm.builder()
                    .roleSn(saved.getRoleSn())
                    .rsrcCd(node.getRsrcCd())
                    .operCd("READ")
                    .grantYn("N")
                    .build());
        }

        permissionService.evictAllPermissions();
        return saved;
    }

    @Auditable(action = "ROLE_UPDATE", resourceType = "ROLE")
    @Transactional
    public Role updateRole(Long roleSn, RoleUpdateRequest req) {
        Role role = roleRepository.findById(roleSn)
                .orElseThrow(() -> new IllegalArgumentException("ROLE_NOT_FOUND: " + roleSn));
        if ("Y".equals(role.getBuiltinYn())) {
            // builtin 역할도 이름/설명/기본여부는 수정 가능 (코드만 보호)
        }
        if (req.roleNm() != null) role.setRoleNm(req.roleNm());
        if (req.roleDc() != null) role.setRoleDc(req.roleDc());
        if (req.colorHex() != null) role.setColorHex(req.colorHex());
        if (req.dfltYn() != null) role.setDfltYn("Y".equalsIgnoreCase(req.dfltYn()) ? "Y" : "N");
        return roleRepository.save(role);
    }

    @Auditable(action = "ROLE_DELETE", resourceType = "ROLE")
    @Transactional
    public void deleteRole(Long roleSn) {
        Role role = roleRepository.findById(roleSn)
                .orElseThrow(() -> new IllegalArgumentException("ROLE_NOT_FOUND: " + roleSn));
        if ("Y".equals(role.getBuiltinYn())) {
            throw new IllegalArgumentException("BUILTIN_ROLE_CANNOT_DELETE: " + role.getRoleCd());
        }
        // 권한, 사용자 매핑 cascade로 자동 삭제
        roleRepository.delete(role);
        permissionService.evictAllPermissions();
    }

    /**
     * 역할의 권한 매트릭스 일괄 갱신.
     * - grantYn=Y/N → upsert
     * - grantYn=null/빈값 → 명시 권한 제거 (트리 상속 모드 복귀)
     */
    @Auditable(action = "PERM_UPDATE", resourceType = "ROLE")
    @Transactional
    public int updatePermissions(Long roleSn, PermissionUpdateRequest req) {
        Role role = roleRepository.findById(roleSn)
                .orElseThrow(() -> new IllegalArgumentException("ROLE_NOT_FOUND: " + roleSn));

        int changed = 0;
        for (PermissionUpdateRequest.PermEntry entry : req.permissions()) {
            String grantYn = entry.grantYn();
            if (grantYn == null || grantYn.isBlank()) {
                // 명시 권한 제거
                permRepository.findByRoleSnAndRsrcCdAndOperCd(roleSn, entry.rsrcCd(), entry.operCd())
                        .ifPresent(p -> permRepository.delete(p));
                changed++;
            } else if ("Y".equalsIgnoreCase(grantYn) || "N".equalsIgnoreCase(grantYn)) {
                Perm perm = permRepository.findByRoleSnAndRsrcCdAndOperCd(roleSn, entry.rsrcCd(), entry.operCd())
                        .orElseGet(() -> Perm.builder()
                                .roleSn(roleSn)
                                .rsrcCd(entry.rsrcCd())
                                .operCd(entry.operCd())
                                .build());
                perm.setGrantYn(grantYn.toUpperCase());
                permRepository.save(perm);
                changed++;
            } else {
                throw new IllegalArgumentException("INVALID_GRANT_YN: " + grantYn);
            }
        }

        permissionService.evictAllPermissions();
        log.info("역할 {} 권한 {}건 갱신", role.getRoleCd(), changed);
        return changed;
    }

    /**
     * 사용자에게 역할 일괄 배정 (전체 교체).
     */
    @Auditable(action = "USER_ROLE_ASSIGN", resourceType = "USER")
    @Transactional
    public List<String> assignUserRoles(UUID userId, List<Long> roleSns) {
        // 기존 매핑 전체 삭제
        userRoleRepository.deleteByUserId(userId);

        // 신규 매핑 생성
        for (Long roleSn : roleSns) {
            roleRepository.findById(roleSn)
                    .orElseThrow(() -> new IllegalArgumentException("ROLE_NOT_FOUND: " + roleSn));
            userRoleRepository.save(UserRole.builder()
                    .userId(userId)
                    .roleSn(roleSn)
                    .build());
        }

        permissionService.evictUserPermissions(userId);
        return userRoleRepository.findRoleCodesByUserId(userId);
    }
}
