package incheon.com.config;

import incheon.com.cmm.context.RequestContextFilter;
import incheon.com.cmm.filter.HTMLTagFilter;
import incheon.com.security.CustomAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
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.web.SecurityFilterChain;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.util.unit.DataSize;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.multipart.support.MultipartFilter;

import javax.servlet.MultipartConfigElement;
import java.util.Arrays;

/**
 * @ClassName : EgovConfigAppSecurity.java
 * @Description : Spring Security 설정
 *
 * @author : 윤주호
 * @since : 2021. 7. 20
 * @version : 1.0
 *
 *          <pre>
 * << 개정이력(Modification Information) >>
 *
 *   수정일              수정자               수정내용
 *  -------------  ------------   ---------------------
 *   2021. 7. 20    윤주호               최초 생성
 *   2025. 9. 26    AI                 SecurityConfig 통합
 *   2025.10. 2     AI                 화이트리스트 통합 관리 (EgovConfigAppWhitelist 제거)
 *          </pre>
 *
 */
@Configuration
@EnableWebSecurity
public class EgovConfigAppSecurity {

    // ========================================
    // 인증 예외 화이트리스트 (Spring Security)
    // ========================================
    // 모든 보안 관련 화이트리스트를 이 클래스에서 중앙 관리
    // - HTTP 요청 URL 경로에 대한 인증 예외 처리
    // - 로그인 없이 접근 가능한 리소스 정의
    // ========================================
    private String[] AUTH_WHITELIST = {
            // === 전체 허용 (개발용) ===
            // "/**", // 모든 경로 인증 없이 허용

            // === 기본 시스템 ===
            "/", // 메인 페이지 (IndexController)
            "/index", // 명시적 인덱스 페이지
            // "/ags/main.do", // [SSO 연동] 제거 - AuthenticationFilter에서 SSO_ID 체크
            "/login/**", // 로그인 관련
            "/auth/**", // 인증 프로세스 (LoginController)
            "/sso/**", // SSO 로그인 처리 (SsoLoginController)
            "/magicsso/**", // SSO 연동 (행정업무포털 MagicSSO)

            // === 파일 관련 ===
            "/file", // 파일 다운로드 (EgovFileDownloadController)
            "/image", // 이미지 미리보기 (EgovImageProcessController)

            // === 개발/테스트 페이지 ===
            "/res/tmp/publ/**", // 레이아웃 테스트 페이지 (TmpPublController)
            "/guide/**", // 개발자 가이드 (GuideController)

            // === 임시 공통 팝업 (ICAPP) ===
            "/ags/ias/user/userSelectPopup.do", // 사용자 선택 팝업
            "/ags/ias/dept/deptSelectPopup.do", // 부서 선택 팝업
            "/api/v1/user/list", // 사용자 목록 조회 API (팝업용)
            "/api/v1/dept/list", // 부서 목록 조회 API (팝업용)

            // === 외부 공개 설문조사 ===
            "/ags/ias/publicSrvy/**", // 외부 공개 설문조사 (시민 대상 - 세션 불필요)

            // === 정적 리소스 ===
            "/css/**", "/js/**", "/images/**", "/fonts/**",
            "/static/**", "/resources/**", "/favicon.ico",
            "/webjars/**", "/videos/**",

            // === API 문서 (Swagger) ===
            "/v3/api-docs/**", "/swagger-resources", "/swagger-resources/**",
            "/swagger-ui.html", "/swagger-ui/**",

            // === 에러 페이지 ===
            "/error", // 에러 페이지

            // === 공통코드 API (테스트용 임시 화이트리스트) ===
            "/api/v1/comCd/**",          // 공통코드 API (TODO: 인증 후 제거)
            "/api/v1/maintenance/**",    // 유지보수 로그인 API (개발 환경 전용)
            "/api/internal/**",          // 내부 시스템 연동 API (약관 등)
            
            // 3D 상태 확인 API (테스트용)
            "/cmm/g3d/dbMngSrvList/api/**",

            // 3D 라벨 서버 및 주소 검색
            "/POISearch/**",
            "/api/v1/kakao/**",
            "/app/**",

            // Mapprime 2D Server
            "/MapPrimeServer/**",

            // Mapprime 3D Builder
            "/MapPrime3DManager/**",

            // 3D 공통 터레인,건물,정사영상
            "/MapPrime3DBuilder/*",

            // 바람길 분석 서버
            "/cmm/g3f/windServer/windspeed/*",
            // 도심정책 결정 지원 파트 바람길 관련 API
            "/pss/simulation/wind/*",

            // 메트릭 수집용
            "/internal/**",

            // "/api/v1/dss/**", //테스트

            // 정책결정 프로젝트 열람 URL
            "/pss/view/map*", // 로그인 없이 접근 가능한 열람 URL 추가
            "/pss/view/**", // 로그인 없이 접근 가능한 열람 URL 추가
            // 도심정책 결정 지원 파트 보행길 분석
            "/pss/biz/modal/*",

            // TODO: 추후 필요한 공개 URL이 있으면 여기에 추가
            // 예시:
            // "/board", // 게시판 목록 (구현 시)
            // "/schedule/daily", // 일정 조회 (구현 시)
            // "/ags/public/**", // AGS 공개 API (구현 시)
            "/rps/jusoPopup.do", // 도로굴착 주소 검색
            "/rps/settingSession.do", // 유실세션 재세팅

            // === 외부망 서비스 화이트리스트 "(회의결과)공간정보 플랫폼 시스템별 명칭_URL 결정.xlsx" ===
            "/aat/**", // 인천시 건축상 수상작
            // "/ags/dss/**", // 도시정책 결정지원 (게스트 권한 구분 필요)
            // "/ags/mrb/**", // 나의 지도 레시피 (게스트 권한 구분 필요)
            // "/drm/**", // 스카이 드론 인천 (게스트 권한 구분 필요)
            "/idt/**", // 3D 기본 페이지
            "/ipd/**", // 아이플러스 지도드림
            "/kfs/**", // 한국최초 인천최고 100선
            "/sgp/**", // 공간정보시스템 일괄 승인
            "/prs/**", // 공공서비스 예약지원
            "/sea/**", // 추후결정
            "/tdr/**", // 인천투어 지도드림
            "/thm/**", // 인천 지도갤러리 imap갤러리

            // === 나의 지도 레시피 외부 공개용 ===
            "/ags/mrb/pub/**",

            // === GIS 2D 공통 ===
            "/cmm/g2f/**",
            "/cmm/g3f/sample/*",

            // === imap ===
            "/sgp/por/*"
    };

    // CORS 허용 도메인 설정
    // TODO: 운영 환경에서는 특정 도메인만 허용하도록 변경 필요
    // TODO: application.properties의 security.cors.allowed-origins로 이동 예정
    // 현재: 개발 편의를 위해 모든 도메인 허용 (임시)
    @Value("${security.cors.allowed-origins:*}")
    private String[] originsWhitelist;

    @Bean
    public incheon.com.security.web.AuthenticationFilter authenticationTokenFilterBean(
            incheon.com.security.service.SecurityUserService securityUserService) throws Exception {
        // 세션 기반 사용자 인증 필터 - SecurityUserService 주입
        return new incheon.com.security.web.AuthenticationFilter(securityUserService);
    }

    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();

        // CORS 설정 - 모든 도메인 허용 (개발/운영 공통)
        // allowCredentials=true 사용 시 allowedOriginPatterns 사용 (allowedOrigins="*"는 불가)
        configuration.setAllowedOriginPatterns(Arrays.asList("*")); // 모든 도메인 패턴 허용
        configuration.setAllowedMethods(Arrays.asList("HEAD", "POST", "GET", "DELETE", "PUT", "PATCH")); // 모든 HTTP 메소드
                                                                                                         // 허용
        configuration.setAllowedHeaders(Arrays.asList("*")); // 모든 헤더 허용
        configuration.setAllowCredentials(true); // 인증 정보 포함 허용 (쿠키, 세션)

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration); // 모든 경로에 CORS 적용
        return source;
    }

    @Bean
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceRequestEncoding(true);
        return characterEncodingFilter;
    }

    @Bean
    public HTMLTagFilter htmlTagFilter() {
        return new HTMLTagFilter();
    }

    @Bean
    public RequestContextFilter incheonRequestContextFilter(
            incheon.ags.ias.comCd.service.ComCdService comCdService,
            Environment environment) {
        return new RequestContextFilter(comCdService, environment);
    }

    // 멀티파트 필터 빈
    @Bean
    public MultipartFilter multipartFilter() {
        return new MultipartFilter();
    }

    // 서블릿 컨테이너에 멀티파트 구성을 제공하기 위한 설정
    @Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setMaxRequestSize(DataSize.ofMegabytes(100L));
        factory.setMaxFileSize(DataSize.ofMegabytes(100L));
        return factory.createMultipartConfig();
    }

    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity http,
            incheon.com.security.service.SecurityUserService securityUserService,
            incheon.ags.ias.comCd.service.ComCdService comCdService,
            CustomAuthenticationEntryPoint customAuthenticationEntryPoint,
            Environment environment) throws Exception {

        return http
                .csrf(AbstractHttpConfigurer::disable)
                // X-Frame-Options 헤더 설정 - iframe 허용
                .headers(headers -> headers
                        .frameOptions().sameOrigin() // 같은 도메인에서 iframe 허용
                )
                .authorizeHttpRequests(authorize -> {
                        // PROD 환경: /auth/users.do 완전 차단 (사용자 목록 노출 방지)
                        if (Arrays.stream(environment.getActiveProfiles()).anyMatch(p -> p.equalsIgnoreCase("prod"))) {
                            authorize.antMatchers("/auth/users.do").denyAll();
                        }

                        authorize
                        // ========================================
                        // 인증 예외 화이트리스트 (공개 접근 허용)
                        // ========================================
                        .antMatchers(AUTH_WHITELIST).permitAll()

                        // ========================================
                        // 기본 보안 정책: 로그인 필수
                        // ========================================
                        .anyRequest().authenticated() // 화이트리스트 외 모든 요청은 인증 필요

                // TODO: Phase 2 - 메뉴 기반 세부 권한 체크 추가 예정
                // .anyRequest().access("isAuthenticated() and
                // @sessionMenuAuthService.hasMenuAccess(request)")

                /*
                 * TODO: 메뉴 접근권한 시스템 통합 계획
                 *
                 * 1. 세션에 사용자별 메뉴 권한 정보 저장
                 * - AuthenticationFilter에서 로그인 시 메뉴 권한 조회
                 * - session.setAttribute("userMenuUrls", userMenuUrls)
                 *
                 * 2. SessionMenuAuthService 구현
                 * - 세션에서 사용자 메뉴 권한 체크
                 * - URL 패턴 매칭 로직 (정확한 매칭 + /** 패턴)
                 *
                 * 3. 권한 변경 시 세션 갱신
                 * - 관리자가 사용자 권한 변경 시 해당 사용자 세션 무효화
                 * - 다음 요청 시 새로운 권한으로 재조회
                 */
                ;})
                .sessionManagement((sessionManagement) -> sessionManagement
                        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
                .cors().and()
                .addFilterBefore(characterEncodingFilter(), ChannelProcessingFilter.class)
                .addFilterBefore(authenticationTokenFilterBean(securityUserService),
                        UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(incheonRequestContextFilter(comCdService, environment),
                        authenticationTokenFilterBean(securityUserService).getClass()) // AuthenticationFilter 이후
                                                                                       // RequestContext 초기화
                .addFilterBefore(multipartFilter(), CsrfFilter.class)
                .exceptionHandling(exceptionHandlingConfigurer -> exceptionHandlingConfigurer
                        .authenticationEntryPoint(customAuthenticationEntryPoint))
                .build();
    }
}