package com.kamco.cd.kamcoback.auth;

import com.kamco.cd.kamcoback.common.enums.RoleType;
import com.kamco.cd.kamcoback.postgres.entity.MenuEntity;
import com.kamco.cd.kamcoback.postgres.repository.menu.MenuRepository;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.function.Supplier;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.stereotype.Component;

/**
 * DB 기반 메뉴 권한 AuthorizationManager
 *
 * <p>- Redis 사용 안 함 - ADMIN 예외 없음 (DB 매핑 기준) - 한 계정 = role 1개 - menu_url(prefix) 기반 API 접근 제어
 */
@Component
@RequiredArgsConstructor
public class MenuAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

  private final MenuRepository menuAuthQueryRepository;

  @Override
  public AuthorizationDecision check(
      Supplier<Authentication> authenticationSupplier, RequestAuthorizationContext context) {

    HttpServletRequest request = context.getRequest();
    String requestPath = normalizePath(request.getRequestURI());

    Authentication authentication = authenticationSupplier.get();
    if (authentication == null || !authentication.isAuthenticated()) {
      return new AuthorizationDecision(false);
    }

    String role = extractSingleRole(authentication);
    if (role == null) {
      return new AuthorizationDecision(false);
    }

    boolean isAdmin = RoleType.ADMIN.getId().equalsIgnoreCase(role);

    // URL별 권한 조회
    List<MenuEntity> matchedMenus = menuAuthQueryRepository.findMenusByRequestPath(requestPath);

    boolean isProtectedUrl = matchedMenus != null && !matchedMenus.isEmpty();

    // URL별 권한에 라벨러, 검수자 권한이 있으면 , ADMIN도 false
    if (isProtectedUrl) {
      List<MenuEntity> allowedMenus = menuAuthQueryRepository.findAllowedMenuUrlsByRole(role);
      if (allowedMenus == null || allowedMenus.isEmpty()) {
        return new AuthorizationDecision(false);
      }

      for (MenuEntity menu : allowedMenus) {
        String baseUri = menu.getMenuUrl();
        if (baseUri == null || baseUri.isBlank()) {
          continue;
        }

        if (matchUri(baseUri, requestPath)) {
          return new AuthorizationDecision(true);
        }
      }
      return new AuthorizationDecision(false);
    }

    // ADMIN은 전부 허용
    if (isAdmin) {
      return new AuthorizationDecision(true);
    }

    // 일반 role은 기존대로 매핑 기반
    List<MenuEntity> allowedMenus = menuAuthQueryRepository.findAllowedMenuUrlsByRole(role);
    if (allowedMenus == null || allowedMenus.isEmpty()) {
      return new AuthorizationDecision(false);
    }

    for (MenuEntity menu : allowedMenus) {
      String baseUri = menu.getMenuUrl();
      if (baseUri == null || baseUri.isBlank()) {
        continue;
      }

      if (matchUri(baseUri, requestPath)) {
        return new AuthorizationDecision(true);
      }
    }

    return new AuthorizationDecision(false);
  }

  /* =========================
   * role 추출
   * ========================= */
  private String extractSingleRole(Authentication authentication) {
    GrantedAuthority ga = authentication.getAuthorities().stream().findFirst().orElse(null);

    if (ga == null || ga.getAuthority() == null || ga.getAuthority().isBlank()) {
      return null;
    }

    String auth = ga.getAuthority();
    return auth.startsWith("ROLE_") ? auth.substring(5) : auth;
  }

  /* =========================
   * URI prefix 매칭
   * ========================= */
  private boolean matchUri(String baseUri, String requestPath) {
    String base = normalizePath(baseUri);
    String req = normalizePath(requestPath);
    return req.equals(base) || req.startsWith(base + "/");
  }

  /* =========================
   * URI 정규화
   * ========================= */
  private String normalizePath(String path) {
    if (path == null || path.isBlank()) {
      return "/";
    }
    return path.replaceAll("//+", "/");
  }
}
