package com.kamco.cd.training.config;

import com.kamco.cd.training.auth.CustomAuthenticationProvider;
import com.kamco.cd.training.auth.JwtAuthenticationFilter;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  public SecurityFilterChain securityFilterChain(
      org.springframework.security.config.annotation.web.builders.HttpSecurity http,
      JwtAuthenticationFilter jwtAuthenticationFilter,
      CustomAuthenticationProvider customAuthenticationProvider)
      throws Exception {

    http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
        .csrf(csrf -> csrf.disable())
        .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .formLogin(form -> form.disable())

        // /monitor 에서 Basic 인증을 쓰려면 disable 하면 안됨
        .httpBasic(basic -> {})
        .logout(logout -> logout.disable())
        .authenticationProvider(customAuthenticationProvider)
        .authorizeHttpRequests(
            auth ->
                auth

                    // monitor
                    .requestMatchers("/monitor/health", "/monitor/health/**")
                    .permitAll()
                    .requestMatchers("/monitor/**")
                    .authenticated() // Basic으로 인증되게끔

                    // mapsheet
                    .requestMatchers("/api/mapsheet/**")
                    .permitAll()
                    .requestMatchers(HttpMethod.POST, "/api/mapsheet/upload")
                    .permitAll()

                    // test role
                    .requestMatchers("/api/test/admin")
                    .hasRole("ADMIN")
                    .requestMatchers("/api/test/label")
                    .hasAnyRole("ADMIN", "LABELER")
                    .requestMatchers("/api/test/review")
                    .hasAnyRole("ADMIN", "REVIEWER")

                    // common permit
                    .requestMatchers("/error")
                    .permitAll()
                    .requestMatchers(HttpMethod.OPTIONS, "/**")
                    .permitAll()
                    .requestMatchers(
                        "/api/auth/signin",
                        "/api/auth/refresh",
                        "/api/auth/logout",
                        "/swagger-ui/**",
                        "/v3/api-docs/**",
                        "/api/upload/chunk-upload-dataset",
                        "/api/upload/chunk-upload-complete",
                        "/download_progress_test.html",
                        "/api/models/download/**")
                    .permitAll()
                    .requestMatchers("/api/members/*/password")
                    .authenticated()
                    // default
                    .anyRequest()
                    .authenticated())

        // JWT 필터는 앞단에
        .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

    return http.build();
  }

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

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  /** CORS 설정 - application.yml에서 환경별로 관리 */
  @Bean
  public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration(); // CORS 객체 생성

    // application.yml에서 환경별로 설정된 도메인 사용
    config.setAllowedOriginPatterns(List.of("*")); // 도메인 허용

    config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(List.of("*")); // 헤더요청 Authorization, Content-Type, X-Custom-Header
    config.setAllowCredentials(true); // 쿠키, Authorization 헤더, Bearer Token 등 자격증명 포함 요청을 허용할지 설정
    config.setExposedHeaders(List.of("Content-Disposition", "Authorization"));
    config.setMaxAge(3600L); // Preflight 요청 캐시 (1시간)

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    /** "/**" → 모든 API 경로에 대해 이 CORS 규칙을 적용 /api/** 같이 특정 경로만 지정 가능. */
    source.registerCorsConfiguration("/**", config); // CORS 정책을 등록
    return source;
  }

  @Bean
  public HttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowUrlEncodedSlash(true);
    firewall.setAllowUrlEncodedDoubleSlash(true);
    firewall.setAllowUrlEncodedPercent(true);
    firewall.setAllowSemicolon(true);
    return firewall;
  }

  /** 완전 제외(필터 자체를 안 탐) */
  @Bean
  public WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.ignoring().requestMatchers("/api/mapsheet/**", "/api/file-manager/**");
  }
}
