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 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 통합 중복로그인 관리
 *  2025.06.04  |  takensoft   | Redis 트랜잭션 및 타이밍 이슈 해결
 *
 * 통합 로그인 유틸리티
 */
@Component
@RequiredArgsConstructor
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;

    /**
     * 통합 로그인 성공 처리
     */
    public void successLogin(MberVO mber, HttpServletRequest req, HttpServletResponse res) {
        // 로그인 방식 확인
        String loginMode = loginModeService.getLoginMode();

        res.setHeader("loginMode", loginMode);
        try {
            // 로그인 이력 등록
            String loginType = (String) req.getAttribute("loginType");
            if (!"OAUTH2".equals(loginType)) {
                saveLoginHistory(mber, req);
            }
            if ("S".equals(loginMode)) {
                // Redis 기반 중복로그인 관리 적용
                handleSessionLogin(mber, req, res);
            } else {
                // 기존 Redis 기반 관리 유지
                handleJwtLogin(mber, req, res);
            }
        }
        catch (IOException ioe) {
            throw new RuntimeException(ioe);
        }
        catch (Exception e) {
            throw e;
        }
    }

    /**
     * 세션 모드 로그인 처리 - Redis 트랜잭션 개선
     */
    private void handleSessionLogin(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);

        // 세션 생성 및 정보 저장
        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");

        // 토큰만 저장
        if (!loginPolicyService.getPolicy()) {
            String tokenKey = "session_token:" + mber.getMbrId();
            // 기존 토큰 삭제 후 새 토큰 저장
            redisTemplate.delete(tokenKey);
            redisTemplate.opsForValue().set(tokenKey, accessToken, Duration.ofSeconds(session.getMaxInactiveInterval()));
        }

        // OAuth2가 아닌 경우 JSON 응답 전송
        String loginType = (String) req.getAttribute("loginType");
        if (!"OAUTH2".equals(loginType)) {
            try {
                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.setCharacterEncoding("UTF-8");
                res.setStatus(HttpStatus.OK.value());

                String jsonResponse = new ObjectMapper().writeValueAsString(result);
                res.getWriter().write(jsonResponse);
                res.getWriter().flush();
            } catch (Exception e) {
                throw 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) {
        }
    }
}