package com.takensoft.common.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.takensoft.cms.loginPolicy.service.LoginModeService;
import com.takensoft.cms.loginPolicy.service.LoginPolicyService;
import com.takensoft.cms.mber.service.LgnHstryService;
import com.takensoft.cms.mber.vo.LgnHstryVO;
import com.takensoft.cms.mber.vo.MberVO;
import com.takensoft.cms.token.service.RefreshTokenService;
import com.takensoft.cms.token.vo.RefreshTknVO;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author takensoft
 * @since 2025.03.21
 * @modification
 *     since    |    author    | description
 *  2025.03.21  |  takensoft   | 최초 등록
 *  2025.05.28  |  takensoft   | 통합 로그인 적용, 문제 해결
 *  2025.05.29  |  takensoft   | Redis 통합 중복로그인 관리
 *
 * 통합 로그인 유틸리티 - Redis 통합 중복로그인 관리
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class LoginUtil {
    private final LgnHstryService lgnHstryService;
    private final HttpRequestUtil httpRequestUtil;
    private final LoginModeService loginModeService;
    private final RefreshTokenService refreshTokenService;
    private final LoginPolicyService loginPolicyService;
    private final JWTUtil jwtUtil;
    private final SessionUtil sessionUtil;
    private final RedisTemplate<String, String> redisTemplate;

    @Value("${jwt.accessTime}")
    private long JWT_ACCESSTIME;
    @Value("${jwt.refreshTime}")
    private long JWT_REFRESHTIME;
    @Value("${cookie.time}")
    private int COOKIE_TIME;

    /**
     * 통합 로그인 성공 처리 - Redis 기반 중복로그인 관리
     */
    public void successLogin(MberVO mber, HttpServletRequest req, HttpServletResponse res) {
        try {
            // 로그인 이력 등록
            String loginType = (String) req.getAttribute("loginType");
            if (!"OAUTH2".equals(loginType)) {
                saveLoginHistory(mber, req);
            }

            // 로그인 방식 확인
            String loginMode = loginModeService.getLoginMode();
            log.info("통합 로그인 모드: {}, 사용자: {}", loginMode, mber.getMbrId());

            if ("S".equals(loginMode)) {
                // Redis 기반 중복로그인 관리 적용
                handleSessionLogin(mber, req, res);
            } else {
                // 기존 Redis 기반 관리 유지
                handleJwtLogin(mber, req, res);
            }

            res.setHeader("login-type", loginMode);
            log.info("통합 로그인 성공 처리 완료: {}, 모드: {}", mber.getMbrId(), loginMode);
        }
        catch (IOException ioe) {
            log.error("로그인 응답 처리 중 IO 오류", ioe);
            throw new RuntimeException(ioe);
        }
        catch (Exception e) {
            log.error("로그인 처리 중 오류 발생", e);
            throw e;
        }
    }

    /**
     * 세션 모드 로그인 처리 - Redis 기반 중복로그인 관리
     */
    private void handleSessionLogin(MberVO mber, HttpServletRequest req, HttpServletResponse res) throws IOException {
        log.info("세션 모드 로그인 처리 (Redis 통합): {}", mber.getMbrId());

        // JWT 토큰은 생성하되 세션에만 저장
        String accessToken = jwtUtil.createJwt("Authorization",
                mber.getMbrId(), mber.getLgnId(), mber.getMbrNm(),
                (List) mber.getAuthorities(), JWT_ACCESSTIME);

        // 세션 생성 및 정보 저장
        HttpSession session = req.getSession(true);
        session.setAttribute("JWT_TOKEN", accessToken);
        session.setAttribute("mbrId", mber.getMbrId());
        session.setAttribute("mbrNm", mber.getMbrNm());
        session.setAttribute("lgnId", mber.getLgnId());
        session.setAttribute("roles", mber.getAuthorList());
        session.setAttribute("loginType", req.getAttribute("loginType") != null ?
                req.getAttribute("loginType") : "S");

        //중복 로그인 비허용을 Redis로 통합 관리
        if (!loginPolicyService.getPolicy()) {
            handleDuplicateSessionLogin(mber.getMbrId(), session);
        }

        // 응답 데이터 구성 (OAuth2는 JSON 응답 없이 리다이렉트만)
        String loginType = (String) req.getAttribute("loginType");
        if (!"OAUTH2".equals(loginType)) {
            Map<String, Object> result = new HashMap<>();
            result.put("mbrId", mber.getMbrId());
            result.put("mbrNm", mber.getMbrNm());
            result.put("roles", mber.getAuthorList());

            res.setContentType("application/json;charset=UTF-8");
            res.setStatus(HttpStatus.OK.value());
            new ObjectMapper().writeValue(res.getOutputStream(), result);
        }
    }

    /**
     * Redis 기반 세션 중복로그인 관리
     */
    private void handleDuplicateSessionLogin(String mbrId, HttpSession newSession) {
        try {
            String sessionKey = "session:" + mbrId;

            // 기존 세션 확인 및 무효화
            String oldSessionId = redisTemplate.opsForValue().get(sessionKey);
            if (oldSessionId != null && !oldSessionId.equals(newSession.getId())) {
                // 기존 세션 무효화
                sessionUtil.invalidateSessionById(oldSessionId);
            }

            // 새 세션 정보를 Redis에 저장
            redisTemplate.opsForValue().set(sessionKey, newSession.getId(),
                    Duration.ofSeconds(newSession.getMaxInactiveInterval()));

            // 기존 SessionUtil에도 등록 (호환성 유지)
            sessionUtil.registerSession(mbrId, newSession);
        } catch (Exception e) {
            // 실패해도 로그인은 계속 진행
        }
    }

    /**
     * JWT 모드 로그인 처리 - 기존 방식 유지
     */
    private void handleJwtLogin(MberVO mber, HttpServletRequest req, HttpServletResponse res) throws IOException {
        // JWT 토큰 생성
        String accessToken = jwtUtil.createJwt("Authorization", mber.getMbrId(), mber.getLgnId(), mber.getMbrNm(), (List) mber.getAuthorities(), JWT_ACCESSTIME);
        String refreshToken = jwtUtil.createJwt("refresh", mber.getMbrId(), mber.getLgnId(), mber.getMbrNm(), (List) mber.getAuthorities(), JWT_REFRESHTIME);

        // Refresh 토큰 처리
        RefreshTknVO refresh = new RefreshTknVO();
        refresh.setMbrId(mber.getMbrId());

        // 기존 refresh 토큰 삭제
        if (refreshTokenService.findByCheckRefresh(req, refresh)) {
            refreshTokenService.delete(req, refresh);
        }
        refresh.setToken(refreshToken);

        // 응답 헤더 및 쿠키 설정
        res.setHeader("Authorization", accessToken);
        res.addCookie(jwtUtil.createCookie("refresh", refreshToken, COOKIE_TIME));

        // 중복 로그인 비허용일 때 Redis 저장
        if (!loginPolicyService.getPolicy()) {
            redisTemplate.delete("jwt:" + mber.getMbrId());
            redisTemplate.opsForValue().set("jwt:" + mber.getMbrId(), accessToken, JWT_ACCESSTIME, TimeUnit.MILLISECONDS);
        }

        // Refresh 토큰 저장
        refreshTokenService.saveRefreshToken(req, res, refresh, JWT_REFRESHTIME);

        // OAuth2가 아닌 경우만 상태 코드 설정
        String loginType = (String) req.getAttribute("loginType");
        if (!"OAUTH2".equals(loginType)) {
            res.setStatus(HttpStatus.OK.value());
        }
    }

    /**
     * 로그인 이력 저장
     */
    private void saveLoginHistory(MberVO mber, HttpServletRequest req) {
        try {
            String userAgent = httpRequestUtil.getUserAgent(req);

            LgnHstryVO lgnHstryVO = new LgnHstryVO();
            lgnHstryVO.setLgnId(mber.getLgnId());
            lgnHstryVO.setLgnType(mber.getAuthorities().stream()
                    .anyMatch(role -> role.getAuthority().equals("ROLE_ADMIN")) ? "0" : "1");
            lgnHstryVO.setCntnIp(httpRequestUtil.getIp(req));
            lgnHstryVO.setCntnOperSys(httpRequestUtil.getOS(userAgent));
            lgnHstryVO.setDeviceNm(httpRequestUtil.getDevice(userAgent));
            lgnHstryVO.setBrwsrNm(httpRequestUtil.getBrowser(userAgent));

            lgnHstryService.LgnHstrySave(lgnHstryVO);
        } catch (Exception e) {
        }
    }
}