package incheon.com.cmm.interceptor;

import incheon.com.cmm.context.RequestContext;
import incheon.com.menu.mapper.CommonMenuMapper;
import incheon.com.security.vo.LoginVO;
import incheon.com.weblog.service.WeblogService;
import incheon.com.weblog.vo.WeblogVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
@RequiredArgsConstructor
public class WebLogInterceptor implements HandlerInterceptor {

    private final WeblogService webLogService;
    private final CommonMenuMapper menuMapper;

    private static final String START_TIME_ATTR = "weblog.start.time";

    private static String truncate(String value, int maxLength) {
        if (value == null || value.length() <= maxLength) {
            return value;
        }
        return value.substring(0, maxLength);
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) {

        try {
            // 로깅 제외 대상 체크
            if (shouldSkipLogging(request)) {
                return true;
            }

            // 시작 시각 기록
            request.setAttribute(START_TIME_ATTR, System.currentTimeMillis());

        } catch (Exception e) {
            log.error("웹로그 preHandle 처리 중 오류 발생", e);
        }

        return true;
    }

    /**
     * 웹로그 수집 제외 대상 체크
     */
    private boolean shouldSkipLogging(HttpServletRequest request) {
        String method = request.getMethod();

        // 1. OPTIONS 메서드 (CORS preflight) 제외
        if ("OPTIONS".equalsIgnoreCase(method)) {
            return true;
        }

        // 2. RequestContext가 설정되지 않은 요청 제외
        //    (RequestContextFilter.shouldNotFilter()에서 제외된 리소스성 요청)
        if (RequestContext.getRequestUri() == null) {
            return true;
        }

        String uri = request.getRequestURI();

        // 3. 고빈도 유틸리티 API 제외
        // 공통코드 조회
        if (uri.startsWith("/api/v1/comCd/")) {
            return true;
        }

        // 페이지네이션 렌더링
        if (uri.equals("/com/pagination/render.do")) {
            return true;
        }

        // 파일 상세 조회
        if (uri.equals("/api/v1/comfile/dtl")) {
            return true;
        }

        // 파일 업로드
        if (uri.equals("/api/v1/comfile/upload")) {
            return true;
        }

        // 파일 확정
        if (uri.equals("/api/v1/comfile/confirm")) {
            return true;
        }

        // 지도출력 로그 API (Controller에서 직접 저장하므로 제외)
        if (uri.equals("/api/v1/com/log/mapOutput")) {
            return true;
        }

        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) {
        /* 필요하면 여기에 추가 로직 작성 */
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) {

        try {
            // 로깅 제외 대상 체크 (preHandle에서 제외된 요청은 startTime이 없음)
            Object startAttr = request.getAttribute(START_TIME_ATTR);
            if (startAttr == null) {
                return;  // preHandle에서 제외된 요청
            }

            long startTime = (Long) startAttr;
            long processingTime = System.currentTimeMillis() - startTime;

            String requestUrl  = request.getRequestURL().toString();
            String requestUri  = request.getRequestURI();
            String httpMethod  = request.getMethod();
            String remoteAddr  = request.getRemoteAddr();
            String sessionId   = (request.getSession(false) != null)
                    ? request.getSession().getId() : "";
            int    statusCode  = response.getStatus();
            String userAgent   = request.getHeader("User-Agent");
            String referer     = request.getHeader("Referer");

            String deviceType = (userAgent != null && userAgent.toLowerCase().contains("mobi"))
                    ? "MOBILE" : "PC";

            // [고객요청] 분석용 Origin(스키마+도메인+포트) 추가
            String origin = request.getScheme() + "://" + request.getServerName()
                    + (request.getServerPort() != 80 && request.getServerPort() != 443
                    ? ":" + request.getServerPort() : "");
            String suffix = " | " + origin;
            String ua = userAgent != null ? userAgent : "";
            int maxUaLen = 500 - suffix.length();
            if (ua.length() > maxUaLen) {
                ua = ua.substring(0, maxUaLen);
            }
            userAgent = ua + suffix;

            // RequestContext에서 사용자 정보 조회
            LoginVO loginVO = RequestContext.getCurrentUser();
            String userId = loginVO != null ? loginVO.getUserId() : null;
            String userName = loginVO != null ? loginVO.getUserNm() : null;
            String deptId = loginVO != null ? loginVO.getDeptCd() : null;
            // [고객요청] 부서명(변별성 부족)을 부서전체명으로 변경
            String deptName = loginVO != null ? loginVO.getDeptWholNm() : null;

            // RequestContext에서 메뉴/시스템 정보 조회
            String sysCd = RequestContext.getCurrentSysCd();
            String sysNm = RequestContext.getCurrentSysNm();
            String menuCd = RequestContext.getCurrentMenuCd();
            String menuNm = RequestContext.getCurrentMenuNm();

            // API 요청 등으로 메뉴 정보가 없는 경우, Referer에서 메뉴 조회
            if ((menuCd == null || sysNm == null) && referer != null && !referer.isEmpty()) {
                try {
                    java.net.URI refererUri = new java.net.URI(referer);
                    String refererPath = refererUri.getPath();

                    if (refererPath != null && !refererPath.isEmpty() && sysCd != null) {
                        Map<String, Object> params = new HashMap<>();
                        params.put("sysCd", sysCd);
                        params.put("requestUri", refererPath);

                        Map<String, Object> menuInfo = menuMapper.selectMenuInfoByUrlPattern(params);
                        if (menuInfo != null) {
                            if (menuCd == null) {
                                menuCd = (String) menuInfo.get("menu_cd");
                            }
                            if (menuNm == null) {
                                menuNm = (String) menuInfo.get("menu_nm");
                            }
                            if (sysNm == null) {
                                sysNm = (String) menuInfo.get("sys_nm");
                            }
                            log.trace("Referer에서 메뉴 정보 조회 성공: menuCd={}, menuNm={}, sysNm={}",
                                    menuCd, menuNm, sysNm);
                        }
                    }
                } catch (Exception e) {
                    log.debug("Referer에서 메뉴 정보 조회 실패: {}", e.getMessage());
                }
            }

            WeblogVO vo = new WeblogVO();
            vo.setDmndUrlAddr(truncate(requestUri, 500));   // 요청 URL 주소 (포트 뒤 경로)
            vo.setDmndMethCd(httpMethod);            // 요청 메서드
            vo.setSysCd(sysCd);                      // 시스템 코드
            vo.setSysNm(sysNm);                      // 시스템 명
            vo.setMenuCd(menuCd);                    // 메뉴 코드
            vo.setMenuNm(menuNm);                    // 메뉴 명
            vo.setUserId(userId);                    // 사용자 ID
            vo.setUserNm(userName);                  // 사용자 명
            vo.setDeptCd(deptId);                    // 부서 코드
            vo.setDeptNm(deptName);                  // 부서 명
            vo.setCntnIp(remoteAddr);                // 접속 IP
            vo.setSesionId(sessionId);               // 세션 ID
            vo.setCntnDt(LocalDateTime.now());       // 접속 일시
            vo.setRspnsStts(statusCode);             // 응답 상태
            vo.setPrcsHr(processingTime);            // 처리 시간
            vo.setUserAgen(userAgent);               // User Agent
            vo.setDmndRefe(truncate(referer, 500));         // Referer
            vo.setDeviceTypeNm(deviceType);          // 디바이스 타입명
            vo.setFrstRegId("SYSTEM");               // 최초 등록자 ID
            vo.setFrstRegDt(LocalDateTime.now());    // 최초 등록 일시
            vo.setLastMdfcnId("SYSTEM");             // 최종 수정자 ID
            vo.setLastMdfcnDt(LocalDateTime.now());  // 최종 수정 일시

            webLogService.insertWebLog(vo);

        } catch (Exception e) {
            log.error("웹로그 afterCompletion 처리 중 오류 발생", e);
        }
    }
}
