package com.bio.bio_backend.global.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
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 com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.servlet.Filter;
import com.bio.bio_backend.domain.base.member.service.MemberService;
import com.bio.bio_backend.global.exception.CustomAuthenticationFailureHandler;
import com.bio.bio_backend.global.exception.JwtAccessDeniedHandler;
import com.bio.bio_backend.global.exception.JwtAuthenticationEntryPoint;
import com.bio.bio_backend.global.utils.JwtUtils;
import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurity {

    private final MemberService memberService;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
    private final JwtUtils jwtUtils;
    private final ObjectMapper objectMapper;
    private final Environment env;
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

    private JwtTokenIssuanceFilter getJwtTokenIssuanceFilter(AuthenticationManager authenticationManager) throws Exception {
        JwtTokenIssuanceFilter filter = new JwtTokenIssuanceFilter(authenticationManager, memberService, jwtUtils, env, objectMapper);
        filter.setFilterProcessesUrl("/login"); // 로그인 EndPoint
        filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler(objectMapper));
        return filter;
    }

    private Filter getJwtTokenValidationFilter() {
        return new JwtTokenValidationFilter(jwtUtils, memberService, env);
    }
    
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        // AuthenticationManager 설정
        AuthenticationManagerBuilder authenticationManagerBuilder =
                http.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.userDetailsService(memberService).passwordEncoder(bCryptPasswordEncoder);

        AuthenticationManager authenticationManager = authenticationManagerBuilder.build();

        http.csrf(AbstractHttpConfigurer::disable)          //csrf 비활성화
                .authorizeHttpRequests(request ->               //request 허용 설정
                        request
                            .requestMatchers("/api/auth/**").permitAll()  // 인증 관련 엔드포인트 허용
                            .requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll()  // Swagger 허용
                            .requestMatchers("/ws/**").permitAll()  // WebSocket 허용
                            .anyRequest().authenticated()  // 나머지 요청은 인증 필요
                )
                .authenticationManager(authenticationManager)
                .sessionManagement(session -> 
                    session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)  // 세션 사용 안함
                )
                .logout(AbstractHttpConfigurer::disable);

        // 예외 처리 핸들링
        http.exceptionHandling((exceptionConfig) ->
                exceptionConfig
                        .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                        .accessDeniedHandler(jwtAccessDeniedHandler)
        );

        http
            // 1단계: JWT 토큰 발급 필터 (로그인 요청 처리 및 토큰 발급)
            .addFilterBefore(getJwtTokenIssuanceFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)
            // 2단계: JWT 토큰 검증 필터 (모든 요청에 대해 토큰 유효성 검증)
            .addFilterBefore(getJwtTokenValidationFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}
