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.client.authentication.OAuth2AuthenticationToken;
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 {
            log.info("OAuth2 로그인 성공 핸들러 시작");

            // OAuth2User 타입 확인 및 정보 추출
            OAuth2UserInfo userInfo = extractUserInfo(authentication);

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

            log.info("OAuth2 사용자 정보 추출 성공 - Provider: {}, Email: {}", userInfo.provider, userInfo.email);

            // 현재 설정된 로그인 모드 확인
            String currentLoginMode = loginModeService.getLoginMode();
            log.info("현재 로그인 모드: {}", currentLoginMode);

            // 통합 로그인 서비스를 통한 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);

            // ★★★ 중요: 메인 페이지로 리다이렉트 (login.page가 아닌 /)
            String redirectUrl = String.format("%s/?oauth_success=true&loginMode=%s", frontUrl, currentLoginMode);
            log.info("OAuth2 성공 리다이렉트: {}", redirectUrl);

            getRedirectStrategy().sendRedirect(request, response, redirectUrl);

        } catch (Exception e) {
            log.error("OAuth2 로그인 처리 중 오류", e);
            handleOAuth2Error(response, e);
        }
    }

    /**
     * OAuth2User에서 사용자 정보 추출 (개선된 버전)
     */
    private OAuth2UserInfo extractUserInfo(Authentication authentication) {
        Object principal = authentication.getPrincipal();
        String provider = determineProvider(authentication);

        log.info("Principal 타입: {}, Provider: {}", principal.getClass().getSimpleName(), provider);

        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, provider);

            } else if (principal instanceof OAuth2User) {
                // 일반 OAuth2 사용자
                OAuth2User oauth2User = (OAuth2User) principal;
                return extractOAuth2UserInfo(oauth2User, provider);
            } else {
                log.error("지원하지 않는 Principal 타입: {}", principal.getClass());
                return null;
            }
        } catch (Exception e) {
            log.error("사용자 정보 추출 중 오류", e);
            return null;
        }
    }

    /**
     * OIDC 사용자 정보 추출 (구글)
     */
    private OAuth2UserInfo extractOidcUserInfo(OidcUser oidcUser, String provider) {
        try {
            log.info("OIDC 사용자 정보 추출 시작 - Provider: {}", provider);

            String id = oidcUser.getSubject(); // OIDC의 subject가 사용자 ID
            String name = oidcUser.getFullName();
            if (name == null || name.trim().isEmpty()) {
                name = oidcUser.getGivenName();
            }
            if (name == null || name.trim().isEmpty()) {
                name = oidcUser.getEmail();
            }
            String email = oidcUser.getEmail();

            log.info("OIDC 정보 - ID: {}, Name: {}, Email: {}", id, name, email);

            return new OAuth2UserInfo(provider, id, name, email);
        } catch (Exception e) {
            log.error("OIDC 사용자 정보 추출 실패", e);
            return null;
        }
    }

    /**
     * 일반 OAuth2 사용자 정보 추출
     */
    private OAuth2UserInfo extractOAuth2UserInfo(OAuth2User oauth2User, String provider) {
        try {
            log.info("OAuth2 사용자 정보 추출 시작 - Provider: {}", provider);
            Map<String, Object> attributes = oauth2User.getAttributes();
            log.info("OAuth2 Attributes: {}", attributes.keySet());

            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");
                    if (id == null) id = (String) attributes.get("id");
                    name = (String) attributes.get("name");
                    email = (String) attributes.get("email");
                    break;
            }

            log.info("추출된 정보 - ID: {}, Name: {}, Email: {}", id, name, email);
            return new OAuth2UserInfo(provider, id, name, email);
        } catch (Exception e) {
            log.error("OAuth2 사용자 정보 추출 실패", e);
            return null;
        }
    }

    /**
     * 제공자 결정
     */
    private String determineProvider(Authentication authentication) {
        if (authentication instanceof OAuth2AuthenticationToken) {
            OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication;
            String registrationId = oauth2Token.getAuthorizedClientRegistrationId();
            log.info("OAuth2 Registration ID: {}", registrationId);
            return registrationId.toLowerCase(); // 소문자로 통일
        }

        String name = authentication.getName();
        log.info("Authentication Name: {}", name);

        if (name != null) {
            String lowerName = name.toLowerCase();
            if (lowerName.contains("google")) return "google";
            if (lowerName.contains("kakao")) return "kakao";
            if (lowerName.contains("naver")) return "naver";
        }

        // 기본값
        return "google";
    }

    /**
     * 로그인 이력 저장 - OAuth2 전용
     */
    private void saveLoginHistory(HttpServletRequest request, MberVO mber, String provider) {
        try {
            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.setCntnOperSysm(httpRequestUtil.getOS(userAgent));
            loginHistory.setDvcNm(httpRequestUtil.getDevice(userAgent));
            loginHistory.setBrwsrNm(httpRequestUtil.getBrowser(userAgent));

            lgnHstryService.LgnHstrySave(loginHistory);
        } catch (Exception e) {
            log.error("로그인 이력 저장 실패", e);
        }
    }

    /**
     * OAuth2 오류 처리
     */
    private void handleOAuth2Error(HttpServletResponse response, Exception e) throws IOException {
        String message = URLEncoder.encode("OAuth 로그인에 실패했습니다.", "UTF-8");
        String errorUrl = String.format("%s/?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;
        }
    }
}