package com.zioinfo.kintex.auth;

import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import com.zioinfo.kintex.tenant.EventTenantMapper;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
 * 행사 단위 RBAC 접근 가드. 컨트롤러가 도메인 액션 전에 호출한다.
 * 홀매니저(킨텍스 내부)는 전 행사 열람+승인이 허용된다.
 *
 * <p>멀티테넌시 2단계(§1A-3-2): 행사 접근은 <b>테넌트 소속 일치</b>를 추가로 강제한다(fail-closed).
 * 대상 행사가 요청자({@code principal.tenantId()})와 다른 테넌트에 속하면 거부한다 — 홀매니저처럼
 * 전 행사 접근권이 넓은 주체의 교차-테넌트 열람을 차단하는 최종 방어선. 대상 행사의 테넌트가
 * 미확인(행사 미존재)이거나 요청자와 동일하면 기존 동작을 그대로 유지한다(기준 테넌트 회귀 0).
 */
@Component
public class EventAccessGuard {

    private final EventTenantMapper eventTenantMapper;

    public EventAccessGuard(EventTenantMapper eventTenantMapper) {
        this.eventTenantMapper = eventTenantMapper;
    }

    /** 인증 필수 — principal 없으면 401. */
    public KintexPrincipal require(KintexPrincipal principal) {
        if (principal == null) {
            throw new ApiException(ErrorCode.UNAUTHORIZED);
        }
        return principal;
    }

    /** 해당 행사 접근 권한 확인(멤버 또는 홀매니저) + 테넌트 소속 일치. 없으면 403. */
    public KintexPrincipal requireEventAccess(KintexPrincipal principal, String eventId) {
        require(principal);
        if (!principal.hasAccess(eventId)) {
            throw new ApiException(ErrorCode.FORBIDDEN);
        }
        assertTenantOwnsEvent(principal, eventId);
        return principal;
    }

    /**
     * 대상 행사가 요청자의 테넌트에 속하는지 검증(fail-closed, §1A-3-2).
     * 행사 미존재(null) 또는 동일 테넌트면 통과 — 기준 테넌트('kintex') 단일 운영 회귀 0.
     */
    private void assertTenantOwnsEvent(KintexPrincipal principal, String eventId) {
        if (eventId == null) {
            return;
        }
        String eventTenant = eventTenantMapper.findEventTenant(eventId);
        // 테넌트 코드 대소문자 정규화 비교 — 표준 대문자 코드('KINTEX')와 레거시 소문자('kintex') 혼재 하위호환.
        if (eventTenant != null && !eventTenant.equalsIgnoreCase(principal.tenantId())) {
            throw new ApiException(ErrorCode.FORBIDDEN);
        }
    }

    /** 해당 행사에서 허용된 역할 중 하나여야 함. 없으면 403. */
    public KintexPrincipal requireRole(KintexPrincipal principal, String eventId, EventRole... allowed) {
        requireEventAccess(principal, eventId);
        EventRole role = principal.roleFor(eventId);
        Set<EventRole> allowedSet = Set.of(allowed);
        if (role == null || !allowedSet.contains(role)) {
            throw new ApiException(ErrorCode.FORBIDDEN);
        }
        return principal;
    }
}
