package com.yaoyuan.jiscuss.config;

import com.yaoyuan.jiscuss.handler.CustomAccessDeniedHandler;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.DefaultHttpSecurityExpressionHandler;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;

/**
 * Spring Security 6 configuration (Spring Boot 3 compatible).
 *
 * <p>Breaking changes from Spring Boot 2.x:
 * <ul>
 *   <li>{@code WebSecurityConfigurerAdapter} removed → use {@code SecurityFilterChain} bean</li>
 *   <li>{@code antMatchers} → {@code requestMatchers}</li>
 *   <li>{@code @EnableGlobalMethodSecurity} → {@code @EnableMethodSecurity}</li>
 *   <li>{@code @Configurable} (AspectJ) corrected to {@code @Configuration}</li>
 * </ul>
 */
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig {

    private final CustomAccessDeniedHandler customAccessDeniedHandler;
    private final UserDetailsService userDetailsService;
    private final ApplicationContext applicationContext;

    /** Controlled by spring.h2.console.enabled — only true in dev (h2) profile. */
    @Value("${spring.h2.console.enabled:false}")
    private boolean h2ConsoleEnabled;

    public WebSecurityConfig(
            CustomAccessDeniedHandler customAccessDeniedHandler,
            @Qualifier("userDetailServiceImpl") UserDetailsService userDetailsService,
            ApplicationContext applicationContext) {
        this.customAccessDeniedHandler = customAccessDeniedHandler;
        this.userDetailsService = userDetailsService;
        this.applicationContext = applicationContext;
    }

    /**
     * Main security filter chain.
     *
     * <p>Security decisions:
     * <ul>
     *   <li>H2 console: only accessible when enabled (dev profile) and requires ROLE_ADMIN</li>
        *   <li>Druid monitoring: requires ROLE_ADMIN through Spring Security</li>
     *   <li>Admin/Actuator paths: requires ROLE_ADMIN</li>
     *   <li>SpringDoc UI paths: public (API docs are for developers)</li>
     *   <li>All other authenticated requests: evaluated by {@code RbacPermission.hasPermission}</li>
     *   <li>X-Frame-Options: SAMEORIGIN (prevents clickjacking while allowing H2 console iframe)</li>
     * </ul>
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // Fix: SAMEORIGIN instead of disable() — prevents clickjacking, allows H2 iframe in same origin
        http.headers(headers -> headers
                .frameOptions(frame -> frame.sameOrigin())
        );

        // CSRF: disable for H2 console path only (H2 web console does not send CSRF tokens)
        if (h2ConsoleEnabled) {
            http.csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**"));
        }

        // Build the RBAC expression manager with ApplicationContext so @bean references work
        DefaultHttpSecurityExpressionHandler expressionHandler = new DefaultHttpSecurityExpressionHandler();
        expressionHandler.setApplicationContext(applicationContext);
        WebExpressionAuthorizationManager rbacManager = new WebExpressionAuthorizationManager(
                "@rbacPermission.hasPermission(request, authentication)");
        rbacManager.setExpressionHandler(expressionHandler);

        http.authorizeHttpRequests(auth -> {
            // Publicly accessible
            auth.requestMatchers(
                    "/css/**", "/js/**", "/images/**", "/static/**", "/favicon.ico",
                    "/login/**", "/register", "/registerDo", "/initUserData",
                    "/", "/main", "/index", "/getdiscussionsbyid",
                    // SpringDoc OpenAPI UI and spec endpoint (developer tools, no sensitive data)
                    "/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**"
            ).permitAll();

            // H2 console: admin-only, dev profile only
            if (h2ConsoleEnabled) {
                auth.requestMatchers("/h2-console/**").hasRole("ADMIN");
            }

            // Druid monitoring, Actuator, Admin UI: restricted to ROLE_ADMIN
            auth.requestMatchers("/druid/**").hasRole("ADMIN");
            auth.requestMatchers("/actuator/**").hasRole("ADMIN");
            auth.requestMatchers("/admin/**").hasRole("ADMIN");

            // All remaining requests evaluated by RBAC permission bean
            auth.anyRequest().access(rbacManager);
        });

        http.formLogin(form -> form
                .loginPage("/login")
                .loginProcessingUrl("/login")
                .usernameParameter("username")
                .passwordParameter("password")
                .defaultSuccessUrl("/index")
        );

        http.logout(logout -> logout
                .logoutSuccessUrl("/login?logout")
        );

        http.exceptionHandling(ex -> ex
                .accessDeniedHandler(customAccessDeniedHandler)
        );

        return http.build();
    }

    /**
     * Authentication manager wired with BCrypt — replaces the old
     * {@code configure(AuthenticationManagerBuilder)} override.
     */
    @Bean
    public AuthenticationManager authenticationManager(BCryptPasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder);
        return new ProviderManager(provider);
    }

    /** BCrypt password encoder bean. */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

