package incheon.com.cmm.context;

import incheon.com.security.vo.LoginVO;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;

/**
 * ThreadLocal 기반 요청 컨텍스트 관리
 *
 * 모든 레이어(Controller, Service, Mapper)에서 현재 요청의 컨텍스트 정보에 접근 가능
 * - 사용자 정보 (LoginVO)
 * - 메뉴/시스템 정보
 * - HTTP 요청 정보
 */
@Slf4j
public class RequestContext {

    /**
     * ThreadLocal 컨텍스트 홀더
     * 각 스레드마다 독립적인 RequestContextHolder 인스턴스 보유
     */
    private static final ThreadLocal<RequestContextHolder> contextHolder = new ThreadLocal<>();

    /**
     * 기본 생성자 - private (정적 메서드만 사용)
     */
    private RequestContext() {
        throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
    }

    // ========================================
    // 컨텍스트 초기화 및 정리
    // ========================================

    /**
     * 컨텍스트 설정 (Filter에서 호출)
     *
     * @param holder 요청 컨텍스트 홀더
     */
    public static void set(RequestContextHolder holder) {
        contextHolder.set(holder);
    }

    /**
     * 컨텍스트 가져오기
     * 컨텍스트가 초기화되지 않은 경우 경고 로그 출력 후 빈 컨텍스트 생성
     *
     * @return 요청 컨텍스트 홀더
     */
    public static RequestContextHolder get() {
        RequestContextHolder holder = contextHolder.get();
        if (holder == null) {
            log.warn("RequestContext가 초기화되지 않았습니다. 빈 컨텍스트를 생성합니다. " +
                    "Filter 설정을 확인하세요.");
            holder = new RequestContextHolder();
            contextHolder.set(holder);
        }
        return holder;
    }

    /**
     * 컨텍스트 정리 (Filter의 finally 블록에서 필수 호출)
     * ThreadLocal 메모리 누수 방지
     */
    public static void clear() {
        contextHolder.remove();
    }

    // ========================================
    // 사용자 정보 관련 메서드
    // ========================================

    /**
     * 현재 로그인 사용자 조회
     *
     * @return 로그인 사용자 정보 (로그인하지 않은 경우 null)
     */
    public static LoginVO getCurrentUser() {
        return get().getLoginUser();
    }

    /**
     * 현재 사용자 ID 조회
     *
     * @return 사용자 ID (로그인하지 않은 경우 null)
     */
    public static String getCurrentUserId() {
        LoginVO user = getCurrentUser();
        return user != null ? user.getUserId() : null;
    }

    /**
     * 현재 사용자 이름 조회
     *
     * @return 사용자 이름 (로그인하지 않은 경우 null)
     */
    public static String getCurrentUserName() {
        LoginVO user = getCurrentUser();
        return user != null ? user.getUserNm() : null;
    }

    /**
     * 현재 사용자 부서 코드 조회
     *
     * @return 부서 코드 (로그인하지 않은 경우 null)
     */
    public static String getCurrentUserDeptCd() {
        LoginVO user = getCurrentUser();
        return user != null ? user.getDeptCd() : null;
    }

    /**
     * 로그인 여부 확인
     *
     * @return true: 로그인됨, false: 미로그인
     */
    public static boolean isAuthenticated() {
        return get().isAuthenticated();
    }

    /**
     * 사용자 정보 설정 (Filter/Interceptor에서 호출)
     *
     * @param user 로그인 사용자 정보
     */
    public static void setLoginUser(LoginVO user) {
        get().setLoginUser(user);
    }

    /**
     * 특정 시스템의 특정 권한을 가지고 있는지 확인
     *
     * @param sysCd 시스템 코드
     * @param authrtCd 권한 코드
     * @return 권한 보유 여부
     */
    public static boolean hasAuthority(String sysCd, String authrtCd) {
        LoginVO user = getCurrentUser();
        return user != null && user.hasAuthrt(sysCd, authrtCd);
    }

    // ========================================
    // 메뉴 정보 관련 메서드
    // ========================================

    /**
     * 현재 활성 메뉴 코드 조회
     *
     * @return 메뉴 코드 (설정되지 않은 경우 null)
     */
    public static String getCurrentMenuCd() {
        return get().getCurrentMenuCd();
    }

    /**
     * 현재 활성 메뉴 명 조회
     *
     * @return 메뉴 명 (설정되지 않은 경우 null)
     */
    public static String getCurrentMenuNm() {
        return get().getCurrentMenuNm();
    }

    /**
     * 메뉴 정보 일괄 설정 (Interceptor에서 호출)
     *
     * @param menuCd 메뉴 코드
     * @param menuNm 메뉴 명
     * @param sysNm 시스템 명
     */
    public static void setMenuInfo(String menuCd, String menuNm, String sysNm) {
        RequestContextHolder holder = get();
        holder.setCurrentMenuCd(menuCd);
        holder.setCurrentMenuNm(menuNm);
        holder.setCurrentSysNm(sysNm);
    }

    /**
     * 메뉴 코드 설정 (Interceptor에서 호출)
     *
     * @param menuCd 메뉴 코드
     */
    public static void setCurrentMenuCd(String menuCd) {
        get().setCurrentMenuCd(menuCd);
    }

    /**
     * 메뉴 명 설정 (Interceptor에서 호출)
     *
     * @param menuNm 메뉴 명
     */
    public static void setCurrentMenuNm(String menuNm) {
        get().setCurrentMenuNm(menuNm);
    }

    // ========================================
    // 시스템 정보 관련 메서드
    // ========================================

    /**
     * 현재 시스템 코드 조회 (AGS, RES, SGP 등)
     *
     * @return 시스템 코드 (설정되지 않은 경우 null)
     */
    public static String getCurrentSysCd() {
        return get().getCurrentSysCd();
    }

    /**
     * 현재 시스템 명 조회
     *
     * @return 시스템 명 (설정되지 않은 경우 null)
     */
    public static String getCurrentSysNm() {
        return get().getCurrentSysNm();
    }

    /**
     * 시스템 코드 설정 (Filter에서 호출)
     *
     * @param sysCd 시스템 코드
     */
    public static void setCurrentSysCd(String sysCd) {
        get().setCurrentSysCd(sysCd);
    }

    /**
     * 시스템 명 설정 (Filter에서 호출)
     *
     * @param sysNm 시스템 명
     */
    public static void setCurrentSysNm(String sysNm) {
        get().setCurrentSysNm(sysNm);
    }

    // ========================================
    // HTTP 요청 정보 관련 메서드
    // ========================================

    /**
     * 현재 HttpServletRequest 조회
     *
     * @return HttpServletRequest (설정되지 않은 경우 null)
     */
    public static HttpServletRequest getRequest() {
        return get().getRequest();
    }

    /**
     * Request 설정 (Filter에서 호출)
     *
     * @param request HTTP 요청 객체
     */
    public static void setRequest(HttpServletRequest request) {
        RequestContextHolder holder = get();
        holder.setRequest(request);
        holder.setRequestUri(request.getRequestURI());
        holder.setRequestMethod(request.getMethod());
    }

    /**
     * 요청 URI 조회
     *
     * @return 요청 URI (설정되지 않은 경우 null)
     */
    public static String getRequestUri() {
        return get().getRequestUri();
    }

    /**
     * 요청 HTTP Method 조회
     *
     * @return HTTP Method (GET, POST 등)
     */
    public static String getRequestMethod() {
        return get().getRequestMethod();
    }

    /**
     * 클라이언트 IP 주소 조회
     * X-Forwarded-For 헤더 우선 확인 (프록시/로드밸런서 환경 지원)
     *
     * @return 클라이언트 IP 주소 (조회 실패 시 null)
     */
    public static String getRemoteIp() {
        HttpServletRequest request = getRequest();
        if (request == null) {
            return null;
        }

        // X-Forwarded-For 헤더 확인 (프록시 환경)
        String ip = request.getHeader("X-Forwarded-For");
        if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
            // 여러 IP가 있는 경우 첫 번째 IP 반환
            int commaIdx = ip.indexOf(',');
            if (commaIdx > 0) {
                ip = ip.substring(0, commaIdx).trim();
            }
            return ip;
        }

        // X-Real-IP 헤더 확인 (Nginx 등)
        ip = request.getHeader("X-Real-IP");
        if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
            return ip;
        }

        // 기본 RemoteAddr 반환
        return request.getRemoteAddr();
    }

    // ========================================
    // 확장 속성 관련 메서드
    // ========================================

    /**
     * 커스텀 속성 설정
     *
     * @param key 속성 키
     * @param value 속성 값
     */
    public static void setAttribute(String key, Object value) {
        get().setAttribute(key, value);
    }

    /**
     * 커스텀 속성 조회
     *
     * @param key 속성 키
     * @return 속성 값 (없으면 null)
     */
    public static Object getAttribute(String key) {
        return get().getAttribute(key);
    }

    /**
     * 커스텀 속성 제거
     *
     * @param key 속성 키
     */
    public static void removeAttribute(String key) {
        get().removeAttribute(key);
    }

    // ========================================
    // 비동기 작업 지원
    // ========================================

    /**
     * 현재 컨텍스트 스냅샷 생성 (비동기 작업 전파용)
     *
     * 비동기 작업(@Async)에서 컨텍스트를 전파할 때 사용
     * <pre>
     * RequestContextHolder snapshot = RequestContext.snapshot();
     * CompletableFuture.runAsync(() -> {
     *     try {
     *         RequestContext.set(snapshot);
     *         // 비동기 작업 수행
     *     } finally {
     *         RequestContext.clear();
     *     }
     * });
     * </pre>
     *
     * @return 복사된 컨텍스트 홀더
     */
    public static RequestContextHolder snapshot() {
        return get().snapshot();
    }

    // ========================================
    // 유틸리티 메서드
    // ========================================

    /**
     * 컨텍스트 정보 문자열 반환 (디버깅용)
     *
     * @return 컨텍스트 정보
     */
    public static String getContextInfo() {
        RequestContextHolder holder = get();
        return String.format("RequestContext[userId=%s, sysCd=%s, menuCd=%s, uri=%s]",
                holder.getUserId(),
                holder.getCurrentSysCd(),
                holder.getCurrentMenuCd(),
                holder.getRequestUri());
    }
}
