package com.takensoft.common.config;

import com.takensoft.cms.accesCtrl.service.AccesCtrlService;
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.token.service.RefreshTokenService;
import com.takensoft.common.filter.AccesFilter;
import com.takensoft.common.filter.JWTFilter;
import com.takensoft.common.filter.LoginFilter;
import com.takensoft.common.filter.SessionAuthFilter;
import com.takensoft.common.util.HttpRequestUtil;
import com.takensoft.common.exception.CustomAccessDenieHandler;
import com.takensoft.common.exception.CustomAuthenticationEntryPoint;
import com.takensoft.common.util.JWTUtil;
import com.takensoft.common.util.SessionUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;

import jakarta.servlet.http.HttpServletRequest;
import java.util.Collections;

/**
 * @author takensoft
 * @since 2025.01.22
 * @modification
 *     since    |    author    | description
 *  2025.01.22  |  takensoft   | 최초 등록
 *
 * Spring Security 설정을 위한 Config
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    // AuthenticationManager가 인자로 받을 AuthenticationConfiguration 객체 생성자 주입
    private final AuthenticationConfiguration authenticationConfiguration;
    private final JWTUtil jwtUtil;
    private final RefreshTokenService refreshTokenService;
    private final LgnHstryService lgnHstryService;
    private final AccesCtrlService accesCtrlService;
    private final CustomAuthenticationEntryPoint authenticationEntryPoint;
    private final CustomAccessDenieHandler accessDenieHandler;
    private final HttpRequestUtil httpRequestUtil;
    private final AppConfig appConfig;
    private final LoginModeService loginModeService;
    private final LoginPolicyService loginPolicyService;
    private final SessionUtil sessionUtil;

    private static String FRONT_URL;    // 프론트 접근 허용 URL
    private static long JWT_ACCESSTIME; // access 토큰 유지 시간
    private static long JWT_REFRESHTIME; // refresh 토큰 유지 시간
    private static int COOKIE_TIME; // 쿠키 유지 시간

    private final RedisTemplate<String, String> redisTemplate;

    /**
     * @param authenticationConfiguration - 인증 구성 객체
     * @param jwtUtil - JWT 유틸리티 객체
     * @param authenticationEntryPoint - 인증 실패 시 처리 엔트리 포인트
     * @param accessDenieHandler - 접근 거부 처리 핸들러
     * @param fUrl - 프론트엔드 URL (application.yml에서 값을 읽어 옴)
     * @param aTime - JWT 접근 토큰 유효 시간 (application.yml에서 값을 읽어 옴)
     * @param rTime - JWT 리프레시 토큰 유효 시간 (application.yml에서 값을 읽어 옴)
     * @param ctime - 쿠키 유효 시간 (application.yml에서 값을 읽어 옴)
     * @param redisTemplate
*
     */
    public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil, RefreshTokenService refreshTokenService, AccesCtrlService accesCtrlService, AppConfig appConfig,
                          LgnHstryService lgnHstryService, CustomAuthenticationEntryPoint authenticationEntryPoint, CustomAccessDenieHandler accessDenieHandler, HttpRequestUtil httpRequestUtil,
                          LoginModeService loginModeService, LoginPolicyService loginPolicyService, SessionUtil sessionUtil, @Value("${front.url}") String fUrl, @Value("${jwt.accessTime}") long aTime, @Value("${jwt.refreshTime}") long rTime, @Value("${cookie.time}") int ctime, RedisTemplate<String, String> redisTemplate) {

        this.authenticationConfiguration = authenticationConfiguration;
        this.refreshTokenService = refreshTokenService;
        this.lgnHstryService = lgnHstryService;
        this.accesCtrlService = accesCtrlService;
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.accessDenieHandler = accessDenieHandler;
        this.jwtUtil = jwtUtil;
        this.httpRequestUtil = httpRequestUtil;
        this.appConfig = appConfig;
        this.loginModeService = loginModeService;
        this.loginPolicyService = loginPolicyService;
        this.sessionUtil = sessionUtil;
        this.FRONT_URL = fUrl;
        this.JWT_ACCESSTIME = aTime;
        this.JWT_REFRESHTIME = rTime;
        this.COOKIE_TIME = ctime;
        this.redisTemplate = redisTemplate;
    }

    /**
     * @param configuration - 인증 구성 객체
     * @return AuthenticationManager 객체
     * @throws Exception - 인증 관리자 초기화 실패 시
     *
     * AuthenticationManager Bean 등록
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    /**
     * @return BCryptPasswordEncoder 객체
     *
     * 비밀번호 해시 암호화를 위한 Bean 등록
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * @param http - HTTP 보안 설정 객체
     * @return SecurityFilterChain 객체
     * @throws Exception - 보안 필터 체인 초기화 실패 시
     *
     * Spring Security 필터 체인 설정
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.cors((cors) -> cors
                .configurationSource(new CorsConfigurationSource() {
                    @Override
                    public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                        CorsConfiguration configuration = new CorsConfiguration();
                        configuration.setAllowedOrigins(Collections.singletonList(FRONT_URL)); // 허용할 프론트 포트 포함 경로 입력
                        configuration.setAllowedMethods(Collections.singletonList("*")); // 허용할 메소드(GET, POST, PUT 등)
                        configuration.setAllowedHeaders(Collections.singletonList("*")); // 허용할 헤드
                        configuration.setAllowCredentials(true); // 프론트에서 credentials 설정하면 true
                        configuration.setMaxAge(3600L); // 허용을 물고 있을 시간
                        configuration.setExposedHeaders(Collections.singletonList("Authorization")); // 서버에서 JWT를 Authorization에 담아 보내기 위해 허용을 함
                        return configuration;
                    }
                })
        );
        // csrf disable
        http.csrf((auth) -> auth.disable());
        // formLogin disable
        http.formLogin((auth) -> auth.disable());
        // http basic 인증 방식 disable
        http.httpBasic((auth) -> auth.disable());
        // 세션 설정
        http.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        http.exceptionHandling((exception) -> exception
                .authenticationEntryPoint(authenticationEntryPoint) // 접근 권한이 없는 경우에 호출
                .accessDeniedHandler(accessDenieHandler) // 인증되지 않은 상태로 접근 했을 때 호출
        );

        http.authorizeHttpRequests((auth) -> auth
                .requestMatchers("/", "/mbr/**", "/company/**", "/refresh/**", "/sys/**").permitAll() // 회원관련, 시스템 제공 관련, 기업용페이지는 누구나 접근 가능
                .requestMatchers("/admin/**").hasRole("ADMIN") // 관리자 페이지는 ADMIN 권한을 가진 사용자만 접근 가능
                .requestMatchers("/editFileUpload/**", "/fileUpload/**").permitAll() // 에디터 파일 업로드
                .anyRequest().authenticated() // 그 외에는 로그인한 사용자만 접근 가능
//                .anyRequest().permitAll() // 모든 사용자 접근 가능
        );

        // 로그인 방식에 따라 필터 적용 (JWT or 세션)
       /* if ("S".equals(loginModeService.getLoginMode())) {
            http.addFilterBefore(new SessionAuthFilter(jwtUtil, redisTemplate, loginPolicyService), LoginFilter.class);
        } else {

        }*/
        http.addFilterBefore(new JWTFilter(jwtUtil, appConfig, loginPolicyService, redisTemplate), LoginFilter.class);
        http.addFilterBefore(new AccesFilter(accesCtrlService, httpRequestUtil, appConfig), JWTFilter.class); // 아이피 검증
        http.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, refreshTokenService, lgnHstryService, httpRequestUtil,
                 loginModeService, loginPolicyService, sessionUtil, JWT_ACCESSTIME, JWT_REFRESHTIME, COOKIE_TIME, redisTemplate), UsernamePasswordAuthenticationFilter.class); // 로그인 필터

        return http.build();
    }
}