package go.kr.project.system.loginLog.service.impl;

import egovframework.util.ClientInfoUtil;
import go.kr.project.system.loginLog.mapper.LoginLogMapper;
import go.kr.project.system.loginLog.model.LoginLogSearchVO;
import go.kr.project.system.loginLog.model.LoginLogVO;
import go.kr.project.system.loginLog.service.LoginLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * packageName    : go.kr.project.login.service.impl
 * fileName       : LoginLogServiceImpl
 * author         : 시스템 관리자
 * date           : 2025-05-13
 * description    : 로그인 로그 관련 서비스 구현 클래스
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2025-05-13        시스템 관리자       최초 생성
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LoginLogServiceImpl extends EgovAbstractServiceImpl implements LoginLogService {

    private final LoginLogMapper loginLogMapper;

    /**
     * 로그인 성공 로그를 기록합니다.
     * 
     * @param userId 사용자 ID
     * @param userAcnt 사용자 계정
     * @param request HTTP 요청 객체
     * @return 저장된 로그인 로그 정보
     */
    @Override
    @Transactional
    public LoginLogVO recordLoginSuccess(String userId, String userAcnt, HttpServletRequest request) {
        LoginLogVO loginLog = createLoginLog(userId, userAcnt, request);
        loginLog.setSuccessYn("Y");
        loginLog.setFailReason(null);

        loginLogMapper.insertLoginLog(loginLog);
        log.info("로그인 성공 로그 기록: {}", userAcnt);

        return loginLog;
    }

    /**
     * 로그인 실패 로그를 기록합니다.
     * 
     * @param userAcnt 사용자 계정
     * @param failReason 실패 사유
     * @param request HTTP 요청 객체
     * @return 저장된 로그인 로그 정보
     */
    @Override
    @Transactional
    public LoginLogVO recordLoginFailure(String userAcnt, String failReason, HttpServletRequest request) {
        LoginLogVO loginLog = createLoginLog(null, userAcnt, request);
        loginLog.setSuccessYn("N");
        loginLog.setFailReason(failReason);

        loginLogMapper.insertLoginLog(loginLog);
        log.info("로그인 실패 로그 기록: {}, 사유: {}", userAcnt, failReason);

        return loginLog;
    }

    /**
     * 로그인 로그를 조회합니다.
     * 
     * @param logId 로그 ID
     * @return 로그인 로그 정보
     */
    @Override
    public LoginLogVO getLoginLog(String logId) {
        return loginLogMapper.selectLoginLog(logId);
    }

    /**
     * 조건에 맞는 로그인 로그 목록을 조회합니다.
     *
     * @param paramVO 조회 조건
     * @return 로그인 로그 목록
     */
    @Override
    public List<LoginLogVO> getLoginLogList(LoginLogSearchVO paramVO) {
        return loginLogMapper.selectLoginLogList(paramVO);
    }

    /**
     * 조건에 맞는 로그인 로그 개수를 조회합니다.
     *
     * @param paramVO 조회 조건
     * @return 로그인 로그 개수
     */
    @Override
    public int getLoginLogCount(LoginLogSearchVO paramVO) {
        return loginLogMapper.selectLoginLogCount(paramVO);
    }

    /**
     * 로그인 로그 객체를 생성합니다.
     * 
     * @param userId 사용자 ID
     * @param userAcnt 사용자 계정
     * @param request HTTP 요청 객체
     * @return 로그인 로그 객체
     */
    private LoginLogVO createLoginLog(String userId, String userAcnt, HttpServletRequest request) {
        LoginLogVO loginLog = new LoginLogVO();
        loginLog.setUserId(userId);
        loginLog.setUserAcnt(userAcnt);
        loginLog.setLoginDttm(LocalDateTime.now());
        loginLog.setIpAddr(ClientInfoUtil.getClientIp(request));
        loginLog.setLoginType("WEB");

        HttpSession session = request.getSession(false);
        if (session != null) {
            loginLog.setSessionId(session.getId());
        }

        String userAgent = request.getHeader("User-Agent");
        loginLog.setUserAgent(userAgent);

        // 디바이스 정보 추출 및 설정
        loginLog.setDeviceInfo(ClientInfoUtil.extractDeviceInfo(userAgent));

        return loginLog;
    }

    /**
     * 특정 사용자의 최근 로그인 기록을 조회합니다.
     * 
     * @param userAcnt 사용자 계정
     * @param limit 조회할 기록 수
     * @return 최근 로그인 기록 목록
     */
    @Override
    public List<LoginLogVO> getRecentLoginLogs(String userAcnt, int limit) {
        return loginLogMapper.selectRecentLoginLogs(userAcnt, limit);
    }

    /**
     * 특정 기간 내 사용자의 로그인 실패 횟수를 조회합니다.
     * 
     * @param userAcnt 사용자 계정
     * @param hours 조회할 시간 범위(시간 단위)
     * @return 로그인 실패 횟수
     */
    @Override
    public int getLoginFailureCount(String userAcnt, int hours) {
        LocalDateTime startDttm = LocalDateTime.now().minusHours(hours);
        return loginLogMapper.selectLoginFailureCount(userAcnt, startDttm);
    }

    /**
     * 특정 사용자의 다른 디바이스에서의 로그인 기록을 조회합니다.
     * 
     * @param userAcnt 사용자 계정
     * @param deviceInfo 현재 디바이스 정보
     * @param hours 조회할 시간 범위(시간 단위)
     * @return 다른 디바이스에서의 로그인 기록 목록
     */
    @Override
    public List<LoginLogVO> getDifferentDeviceLogins(String userAcnt, String deviceInfo, int hours) {
        LocalDateTime startDttm = LocalDateTime.now().minusHours(hours);
        return loginLogMapper.selectDifferentDeviceLogins(userAcnt, deviceInfo, startDttm);
    }

    /**
     * 특정 IP 주소에서의 로그인 시도 횟수를 조회합니다.
     * 
     * @param ipAddr IP 주소
     * @param hours 조회할 시간 범위(시간 단위)
     * @return 로그인 시도 횟수
     */
    @Override
    public int getLoginAttemptsByIp(String ipAddr, int hours) {
        LocalDateTime startDttm = LocalDateTime.now().minusHours(hours);
        return loginLogMapper.selectLoginAttemptsByIp(ipAddr, startDttm);
    }

    /**
     * 로그인 시도가 비정상적인 패턴인지 확인합니다.
     * 
     * @param userAcnt 사용자 계정
     * @param request HTTP 요청 객체
     * @return 비정상적인 패턴 여부 및 사유를 담은 Map 객체
     */
    @Override
    public Map<String, Object> checkAbnormalLoginPattern(String userAcnt, HttpServletRequest request) {
        Map<String, Object> result = new HashMap<>();
        boolean isAbnormal = false;
        String reason = "";

        // 클라이언트 IP 주소 가져오기
        String ipAddr = ClientInfoUtil.getClientIp(request);

        // 현재 디바이스 정보 추출
        String userAgent = request.getHeader("User-Agent");
        String deviceInfo = ClientInfoUtil.extractDeviceInfo(userAgent);

        // 1. 최근 1시간 내 로그인 실패 횟수 확인 (5회 이상이면 비정상)
        int failureCount = getLoginFailureCount(userAcnt, 1);
        if (failureCount >= 5) {
            isAbnormal = true;
            reason = "최근 1시간 내 로그인 실패 횟수가 5회 이상입니다.";
        }

        // 2. 최근 24시간 내 다른 디바이스에서의 로그인 확인
        List<LoginLogVO> differentDeviceLogins = getDifferentDeviceLogins(userAcnt, deviceInfo, 24);
        if (!differentDeviceLogins.isEmpty()) {
            // 다른 디바이스에서의 로그인이 있으면 비정상으로 간주할 수 있지만,
            // 여기서는 정보만 제공하고 비정상으로 간주하지는 않음
            result.put("differentDeviceLogins", differentDeviceLogins);
        }

        // 3. 최근 1시간 내 동일 IP에서의 로그인 시도 횟수 확인 (20회 이상이면 비정상)
        int ipAttempts = getLoginAttemptsByIp(ipAddr, 1);
        if (ipAttempts >= 20) {
            isAbnormal = true;
            if (!reason.isEmpty()) {
                reason += " ";
            }
            reason += "최근 1시간 내 동일 IP에서의 로그인 시도 횟수가 20회 이상입니다.";
        }

        result.put("isAbnormal", isAbnormal);
        result.put("reason", reason);
        result.put("ipAddr", ipAddr);
        result.put("deviceInfo", deviceInfo);
        result.put("failureCount", failureCount);
        result.put("ipAttempts", ipAttempts);

        return result;
    }

    /**
     * 디바이스 유형별 로그인 통계를 조회합니다.
     * 
     * @param params 조회 조건 (startDate, endDate, successYn 등)
     * @return 디바이스 유형별 로그인 횟수 목록
     */
    @Override
    public List<Map<String, Object>> getDeviceTypeStats(Map<String, Object> params) {
        return loginLogMapper.selectDeviceTypeStats(params);
    }

    /**
     * 로그인 성공/실패 비율 통계를 조회합니다.
     * 
     * @param params 조회 조건 (startDate, endDate, deviceInfo 등)
     * @return 성공/실패 횟수 목록
     */
    @Override
    public List<Map<String, Object>> getSuccessFailStats(Map<String, Object> params) {
        return loginLogMapper.selectSuccessFailStats(params);
    }

    /**
     * 시간대별 로그인 시도 통계를 조회합니다.
     * 
     * @param params 조회 조건 (startDate, endDate, deviceInfo 등)
     * @return 시간대별 로그인 시도 횟수 목록
     */
    @Override
    public List<Map<String, Object>> getHourlyLoginStats(Map<String, Object> params) {
        return loginLogMapper.selectHourlyLoginStats(params);
    }
}
