package com.zioinfo.kintex.config;

import com.zioinfo.kintex.auth.JwtAuthenticationFilter;
import com.zioinfo.kintex.auth.JwtService;
import com.zioinfo.kintex.tenant.TenantContextFilter;
import com.zioinfo.kintex.tenant.TenantResolver;
import com.zioinfo.kintex.common.ApiResponse;
import com.zioinfo.kintex.common.ApiResponse.ApiError;
import com.zioinfo.kintex.common.error.ErrorCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.crypto.password.PasswordEncoder;
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 org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.ArrayList;
import java.util.List;

/**
 * 보안 설정 — 무상태 JWT. 공개 경로: /health, 로그인, WebSocket 핸드셰이크.
 * 그 외는 인증 필요(행사 RBAC는 컨트롤러 가드에서 추가 검사).
 */
@Configuration
public class SecurityConfig {

    /**
     * CORS 허용 오리진 화이트리스트(env {@code CORS_ALLOWED_ORIGINS}, 콤마구분). 와일드카드('*') 금지 —
     * 값에 '*'가 있으면 무시하고 로컬 개발 오리진만 허용한다(운영 오리진은 반드시 명시 주입).
     */
    @Value("${kintex.cors.allowed-origins:http://localhost:5173,http://localhost:3000}")
    private List<String> allowedOrigins;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, JwtService jwtService,
                                           TenantResolver tenantResolver,
                                           ObjectMapper objectMapper,
                                           CorsConfigurationSource corsConfigurationSource) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .cors(cors -> cors.configurationSource(corsConfigurationSource))
                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/health", "/api/auth/login", "/ws/**",
                                "/api/internal/render/callback",
                                "/api/auth/login/secure", "/api/auth/otp/verify",
                                "/api/auth/otp/enroll/setup", "/api/auth/otp/enroll/verify",
                                "/api/auth/register", "/api/auth/password/forgot",
                                "/api/auth/password/reset",
                                // 공개 콘텐츠(로그인 슬라이드 등) + 업로드/렌더 산출 정적 파일
                                "/api/public/**", "/uploads/**", "/renders/**",
                                // F099 인바운드 웹훅 수신(인증 없음 — 토큰 + HMAC 서명 이중 검증으로 방어)
                                "/api/webhooks/in/**").permitAll()
                        .anyRequest().authenticated())
                .exceptionHandling(eh -> eh.authenticationEntryPoint((req, res, ex) -> {
                    // 스택트레이스 미노출 — 표준 봉투로 401만 반환.
                    res.setStatus(ErrorCode.UNAUTHORIZED.status().value());
                    res.setContentType(MediaType.APPLICATION_JSON_VALUE);
                    res.setCharacterEncoding("UTF-8");
                    res.getWriter().write(objectMapper.writeValueAsString(
                            ApiResponse.fail(new ApiError(ErrorCode.UNAUTHORIZED.name(),
                                    ErrorCode.UNAUTHORIZED.defaultMessage()))));
                }))
                // 테넌트 컨텍스트 해소(§1A-3) → 이후 JWT 인증. 미해소 시 기준 테넌트 폴백(회귀 0).
                .addFilterBefore(new TenantContextFilter(tenantResolver, objectMapper),
                        UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JwtAuthenticationFilter(jwtService),
                        UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    /**
     * B11 — CORS 명시(화이트리스트). 와일드카드('*') 오리진은 제거하고 명시 오리진만 허용한다.
     * 인증은 Bearer JWT(쿠키 미사용)이므로 {@code allowCredentials=false} — 자격증명 쿠키 반사 위험을 원천 차단한다.
     * 허용 헤더는 Authorization·Content-Type·테넌트 헤더로 최소화한다.
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        List<String> origins = new ArrayList<>();
        if (allowedOrigins != null) {
            for (String o : allowedOrigins) {
                if (o == null) {
                    continue;
                }
                String v = o.trim();
                // 와일드카드는 금지(운영 오리진 명시 강제) — '*' 단독/포함 값은 무시한다.
                if (!v.isEmpty() && !v.contains("*")) {
                    origins.add(v);
                }
            }
        }
        CorsConfiguration cfg = new CorsConfiguration();
        cfg.setAllowedOrigins(origins);
        cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
        cfg.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-Tenant-Id", "X-Requested-With"));
        cfg.setExposedHeaders(List.of("Content-Disposition"));
        cfg.setAllowCredentials(false);
        cfg.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", cfg);
        return source;
    }

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