package incheon.com.cmm.context;

import incheon.ags.ias.comCd.service.ComCdService;
import incheon.ags.ias.comCd.vo.ComCdVO;
import incheon.com.security.vo.LoginVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.dao.DataAccessException;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.PostConstruct;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * RequestContext 초기화 및 정리 필터
 *
 * 실행 순서:
 * 1. 세션에서 사용자 정보 조회 (AuthenticationFilter 이후)
 * 2. URI에서 시스템 코드 추출 (DB에서 로드한 시스템 코드 목록 기반)
 * 3. RequestContext에 저장 (ThreadLocal)
 * 4. 요청 처리
 * 5. finally 블록에서 반드시 clear() 호출 (메모리 누수 방지)
 *
 * Note: EgovConfigAppSecurity에서 수동 등록
 */
@Slf4j
@RequiredArgsConstructor
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
public class RequestContextFilter extends OncePerRequestFilter {

    private final ComCdService comCdService;
    private final Environment environment;

    /**
     * 유효한 시스템 코드 목록 (DB에서 동적으로 로드)
     */
    private Set<String> validSysCodes = new HashSet<>();

    /**
     * 시스템 코드 → 시스템명 매핑 (sysNm 조회용)
     */
    private Map<String, String> sysCdToNmMap = new HashMap<>();

    /**
     * 기본 시스템 코드 (매칭 실패 시 사용)
     */
    private static final String DEFAULT_SYSTEM_CODE = "AGS";

    /**
     * 애플리케이션 시작 시 DB에서 시스템 코드 목록 로드
     */
    @PostConstruct
    public void init() {
        try {
            refreshSystemCodes();
        } catch (DataAccessException e) {
            throw new IllegalStateException("시스템 코드 초기화 실패. DB 연결을 확인하세요.", e);
        } catch (Exception e) {
            throw new IllegalStateException("시스템 코드 초기화 중 오류 발생", e);
        }
    }

    /**
     * DB에서 활성화된 시스템 코드 목록을 조회하여 캐싱
     */
    private void refreshSystemCodes() throws Exception {
        log.debug("DB에서 시스템 코드 목록 로드 중...");
        List<ComCdVO> systemCodes = comCdService.getSystemCodeList();
        validSysCodes = systemCodes.stream()
                .map(ComCdVO::getCd)
                .collect(Collectors.toSet());

        // 시스템 코드 → 시스템명 매핑 저장
        sysCdToNmMap = systemCodes.stream()
                .collect(Collectors.toMap(ComCdVO::getCd, ComCdVO::getCdNm, (a, b) -> a));

        log.debug("시스템 코드 목록 로드 완료: {}", validSysCodes);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        try {
            // 1. 컨텍스트 홀더 생성
            RequestContextHolder holder = new RequestContextHolder();

            // 2. 세션에서 사용자 정보 조회
            initializeUserInfo(request, holder);

            // 3. 시스템 코드 추출 및 설정
            String sysCd = extractSystemCode(request.getRequestURI());
            holder.setCurrentSysCd(sysCd);
            holder.setCurrentSysNm(sysCdToNmMap.get(sysCd));

            // 4. Request 정보 저장
            holder.setRequest(request);
            holder.setRequestUri(request.getRequestURI());
            holder.setRequestMethod(request.getMethod());

            // 5. ThreadLocal에 저장
            RequestContext.set(holder);

            // 6. 프로필 정보를 Request Attribute로 설정 (JSP에서 사용)
            boolean isProdProfile = Arrays.stream(environment.getActiveProfiles()).anyMatch(p -> p.equalsIgnoreCase("prod"));
            request.setAttribute("isProdProfile", isProdProfile);

            if (log.isTraceEnabled()) {
                log.trace("RequestContext 초기화 완료 - 사용자: {}, 시스템: {}({}), URI: {}",
                        holder.getUserId() != null ? holder.getUserId() : "anonymous",
                        sysCd, holder.getCurrentSysNm(), request.getRequestURI());
            }

            // 7. 다음 필터/인터셉터로 전달
            filterChain.doFilter(request, response);

        } finally {
            RequestContext.clear();
        }
    }

    /**
     * 세션에서 사용자 정보 조회 및 컨텍스트 설정
     *
     * @param request HTTP 요청
     * @param holder 컨텍스트 홀더
     */
    private void initializeUserInfo(HttpServletRequest request, RequestContextHolder holder) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            LoginVO loginVO = (LoginVO) session.getAttribute("loginVO");
            if (loginVO != null) {
                holder.setLoginUser(loginVO);
            }
        }
    }

    /**
     * URI에서 시스템 코드 추출
     *
     * 추출 로직:
     * - URI 첫 번째 경로 세그먼트를 시스템 코드로 판단
     * - 예: /ags/menu/list.do → AGS
     *      /res/road/list.do → RES
     *      /sgp/map/view.do → SGP
     *
     * @param uri 요청 URI
     * @return 시스템 코드 (매칭 실패 시 기본값: AGS)
     */
    private String extractSystemCode(String uri) {
        if (uri == null || uri.isEmpty()) {
            return DEFAULT_SYSTEM_CODE;
        }

        // '/'로 분리
        String[] parts = uri.split("/");
        if (parts.length >= 2) {
            String firstSegment = parts[1].toUpperCase();

            // 유효한 시스템 코드인지 확인
            if (isValidSystemCode(firstSegment)) {
                return firstSegment;
            }
        }

        return DEFAULT_SYSTEM_CODE;
    }

    /**
     * 유효한 시스템 코드 확인 (DB에서 로드한 목록 기반)
     *
     * @param code 시스템 코드
     * @return 유효 여부
     */
    private boolean isValidSystemCode(String code) {
        return validSysCodes.contains(code);
    }

    /**
     * 정적 리소스나 API 문서는 필터 제외 (성능 최적화)
     *
     * 제외 대상:
     * - 정적 리소스 (CSS, JS, 이미지, 폰트 등)
     * - API 문서 (Swagger)
     * - 에러 페이지 (CustomErrorController 처리)
     * - 브라우저 자동 요청 (favicon, .well-known 등)
     *
     * @param request HTTP 요청
     * @return true: 필터 제외, false: 필터 적용
     */
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String uri = request.getRequestURI();

        // 정적 리소스 제외 (RequestContext 초기화 불필요)
        return uri.startsWith("/static/") ||
               uri.startsWith("/css/") ||
               uri.startsWith("/js/") ||
               uri.startsWith("/images/") ||
               uri.startsWith("/fonts/") ||
               uri.startsWith("/webjars/") ||
               uri.startsWith("/resources/") ||
               uri.equals("/favicon.ico") ||
               // API 문서 (Swagger) 제외
               uri.startsWith("/swagger-ui/") ||
               uri.startsWith("/v3/api-docs/") ||
               uri.startsWith("/swagger-resources/") ||
               // 에러 페이지 제외 (CustomErrorController에서 처리)
               uri.startsWith("/error") ||
               // 브라우저 자동 요청 제외 (Chrome DevTools 등)
               uri.startsWith("/.well-known/") ||
               // 파일삭제 구동 제외
               uri.startsWith("/rdm/") ||
               // 3D 지형 데이터 프록시 제외 (Cesium terrain)
                uri.startsWith("/MapPrimeServer/") ||
                uri.startsWith("/MapPrime3DManager/");
    }
}
