package com.takensoft.common.config;

import com.takensoft.cms.accesCtrl.service.AccesCtrlService;
import com.takensoft.cms.mber.service.LgnHstryService;
import com.takensoft.cms.mber.service.RefreshTokenService;
import com.takensoft.common.filter.AccesFilter;
import com.takensoft.common.filter.JWTFilter;
import com.takensoft.common.filter.LoginFilter;
import com.takensoft.common.util.CommonUtils;
import com.takensoft.common.util.CustomAccessDenieHandler;
import com.takensoft.common.util.CustomAuthenticationEntryPoint;
import com.takensoft.common.util.JWTUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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 javax.servlet.http.HttpServletRequest;
import java.util.Collections;

/**
 * @author takensoft
 * @since 2024.04.01
 * 
 * 스프링 시큐리티 설정
 */
@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 CommonUtils commonUtils;
    private final CommonConfig commonConfig;

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

    public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil, RefreshTokenService refreshTokenService, AccesCtrlService accesCtrlService, CommonConfig commonConfig,
                          LgnHstryService lgnHstryService, CustomAuthenticationEntryPoint authenticationEntryPoint, CustomAccessDenieHandler accessDenieHandler, CommonUtils commonUtils,
                          @Value("${front.url}")String fUrl,@Value("${jwt.accessTime}")long aTime, @Value("${jwt.refreshTime}")long rTime, @Value("${cookie.time}")int ctime) {

        this.authenticationConfiguration = authenticationConfiguration;
        this.refreshTokenService = refreshTokenService;
        this.lgnHstryService = lgnHstryService;
        this.accesCtrlService = accesCtrlService;
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.accessDenieHandler = accessDenieHandler;
        this.jwtUtil = jwtUtil;
        this.commonUtils = commonUtils;
        this.commonConfig = commonConfig;

        this.FRONT_URL = fUrl;
        this.JWT_ACCESSTIME = aTime;
        this.JWT_REFRESHTIME = rTime;
        this.COOKIE_TIME = ctime;
    }

    // AuthenticationManager Bean 등록
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    // 해시 암호화
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

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


        http.addFilterBefore(new JWTFilter(jwtUtil, commonConfig), LoginFilter.class); // 토큰 검증 필터
        http.addFilterBefore(new AccesFilter(accesCtrlService, commonUtils, commonConfig), JWTFilter.class); // 아이피 검증
        http.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, refreshTokenService, lgnHstryService, commonUtils,
                        commonConfig, JWT_ACCESSTIME, JWT_REFRESHTIME, COOKIE_TIME), UsernamePasswordAuthenticationFilter.class); // 로그인 필터

        return http.build();
    }
}