package com.takensoft.common.oauth.handler;

import com.takensoft.cms.loginPolicy.service.LoginModeService;
import com.takensoft.cms.mber.service.LgnHstryService;
import com.takensoft.cms.mber.service.UnifiedLoginService;
import com.takensoft.cms.mber.vo.LgnHstryVO;
import com.takensoft.cms.mber.vo.MberVO;
import com.takensoft.common.oauth.vo.CustomOAuth2UserVO;
import com.takensoft.common.util.HttpRequestUtil;
import com.takensoft.common.util.LoginUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.Map;

/**
 * @author takensoft
 * @since 2025.05.22
 * @modification
 *     since    |    author    | description
 *  2025.05.22  |  takensoft   | 최초 등록
 *  2025.05.28  |  takensoft   | 통합 로그인 적용
 *  2025.05.29  |  takensoft   | OAuth2 통합 문제 해결
 *  2025.06.02  |  takensoft   | 세션 모드 중복로그인 처리 개선
 *  2025.06.09  |  takensoft   | OIDC 타입 캐스팅 문제 해결
 *
 * OAuth2 로그인 성공 핸들러
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private final UnifiedLoginService unifiedLoginService;
    private final LgnHstryService lgnHstryService;
    private final HttpRequestUtil httpRequestUtil;
    private final LoginUtil loginUtil;
    private final LoginModeService loginModeService;

    @Value("${front.url}")
    private String frontUrl;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        try {
            // OAuth2User 타입 확인 및 정보 추출
            OAuth2UserInfo userInfo = extractUserInfo(authentication);

            if (userInfo == null) {
                handleOAuth2Error(response, new Exception("사용자 정보 추출 실패"));
                return;
            }

            // 현재 설정된 로그인 모드 확인
            String currentLoginMode = loginModeService.getLoginMode();

            // 통합 로그인 서비스를 통한 OAuth2 사용자 처리
            MberVO mber = unifiedLoginService.processOAuth2User(
                    userInfo.email,
                    unifiedLoginService.convertProviderToMbrType(userInfo.provider),
                    userInfo.id,
                    userInfo.name,
                    request
            );

            // OAuth2 로그인 이력 저장
            saveLoginHistory(request, mber, userInfo.provider);

            request.setAttribute("loginType", "OAUTH2");

            // LoginUtil을 통한 통합 로그인 처리
            loginUtil.successLogin(mber, request, response);

            // OAuth2 성공 후 프론트엔드로 리다이렉트
            String redirectUrl = String.format("%s/login.page?oauth_success=true&loginMode=%s", frontUrl, currentLoginMode);
            getRedirectStrategy().sendRedirect(request, response, redirectUrl);

        } catch (Exception e) {
            handleOAuth2Error(response, e);
        }
    }

    /**
     * OAuth2User에서 사용자 정보 추출 (타입별 처리)
     */
    private OAuth2UserInfo extractUserInfo(Authentication authentication) {
        Object principal = authentication.getPrincipal();

        try {
            if (principal instanceof CustomOAuth2UserVO) {
                // 커스텀 OAuth2 사용자
                CustomOAuth2UserVO customUser = (CustomOAuth2UserVO) principal;
                return new OAuth2UserInfo(
                        customUser.getProvider(),
                        customUser.getId(),
                        customUser.getName(),
                        customUser.getEmail()
                );

            } else if (principal instanceof OidcUser) {
                // OIDC 사용자 (구글)
                OidcUser oidcUser = (OidcUser) principal;
                return extractOidcUserInfo(oidcUser, authentication);

            } else if (principal instanceof OAuth2User) {
                // 일반 OAuth2 사용자
                OAuth2User oauth2User = (OAuth2User) principal;
                return extractOAuth2UserInfo(oauth2User, authentication);
            } else {
                return null;
            }
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * OIDC 사용자 정보 추출 (구글)
     */
    private OAuth2UserInfo extractOidcUserInfo(OidcUser oidcUser, Authentication authentication) {
        try {
            String provider = determineProvider(authentication);
            Map<String, Object> attributes = oidcUser.getAttributes();

            return new OAuth2UserInfo(
                    provider,
                    oidcUser.getSubject(), // OIDC의 subject가 사용자 ID
                    oidcUser.getFullName() != null ? oidcUser.getFullName() : oidcUser.getGivenName(),
                    oidcUser.getEmail()
            );
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 일반 OAuth2 사용자 정보 추출
     */
    private OAuth2UserInfo extractOAuth2UserInfo(OAuth2User oauth2User, Authentication authentication) {
        try {
            String provider = determineProvider(authentication);
            Map<String, Object> attributes = oauth2User.getAttributes();

            String id = null;
            String name = null;
            String email = null;

            // 제공자별 정보 추출
            switch (provider.toLowerCase()) {
                case "kakao":
                    id = String.valueOf(attributes.get("id"));
                    Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
                    if (kakaoAccount != null) {
                        email = (String) kakaoAccount.get("email");
                        Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");
                        if (profile != null) {
                            name = (String) profile.get("nickname");
                        }
                    }
                    break;

                case "naver":
                    Map<String, Object> naverResponse = (Map<String, Object>) attributes.get("response");
                    if (naverResponse != null) {
                        id = (String) naverResponse.get("id");
                        name = (String) naverResponse.get("name");
                        email = (String) naverResponse.get("email");
                    }
                    break;

                case "google":
                    id = (String) attributes.get("sub");
                    name = (String) attributes.get("name");
                    email = (String) attributes.get("email");
                    break;
            }

            return new OAuth2UserInfo(provider, id, name, email);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 제공자 결정
     */
    private String determineProvider(Authentication authentication) {
        // 클라이언트 등록 ID에서 제공자 결정
        String name = authentication.getName();
        if (name != null) {
            if (name.contains("google")) return "google";
            if (name.contains("kakao")) return "kakao";
            if (name.contains("naver")) return "naver";
        }

        // 기본값
        return "google";
    }

    /**
     * 로그인 이력 저장 - OAuth2 전용
     */
    private void saveLoginHistory(HttpServletRequest request, MberVO mber, String provider) {
            String userAgent = httpRequestUtil.getUserAgent(request);

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

            lgnHstryService.LgnHstrySave(loginHistory);
    }

    /**
     * OAuth2 오류 처리
     */
    private void handleOAuth2Error(HttpServletResponse response, Exception e) throws IOException {
        String message = URLEncoder.encode("OAuth 로그인에 실패했습니다.", "UTF-8");
        String errorUrl = String.format("%s/login.page?error=oauth2_failed&message=%s", frontUrl, message);
        getRedirectStrategy().sendRedirect(null, response, errorUrl);
    }

    /**
     * OAuth2 사용자 정보 내부 클래스
     */
    private static class OAuth2UserInfo {
        final String provider;
        final String id;
        final String name;
        final String email;

        OAuth2UserInfo(String provider, String id, String name, String email) {
            this.provider = provider;
            this.id = id;
            this.name = name;
            this.email = email;
        }
    }
}