package com.ffii.fpsms.config.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.config.http.SessionCreationPolicy;
import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory;
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 com.ffii.fpsms.config.security.jwt.JwtRequestFilter;

import org.springframework.http.HttpMethod;

import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    public static final String INDEX_URL = "/";
    public static final String LOGIN_URL = "/login";
    public static final String LDAP_LOGIN_URL = "/ldap-login";

    public static final String[] URL_WHITELIST = {
            INDEX_URL,
            LOGIN_URL,
            LDAP_LOGIN_URL,
            "/refresh-token",
            "/py/**"
    };

    public static final String[] CORS_ALLOWED_METHODS = {
            "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"
    };

    @Lazy
    @Autowired
    private JwtRequestFilter jwtRequestFilter;

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

    @Bean
    @Qualifier("LdapAuthenticationManager")
    public AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource contextSource) {
        LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
        factory.setUserSearchFilter("cn={0}");
        return factory.createAuthenticationManager();
    }

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

    @Bean
    @Order(1)
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .cors(Customizer.withDefaults()).csrf(csrf -> csrf.disable())
                .requestCache(requestCache -> requestCache.disable())
                .authorizeHttpRequests(
                        authRequest -> authRequest
                                .requestMatchers(URL_WHITELIST).permitAll()
                                .requestMatchers(org.springframework.http.HttpMethod.OPTIONS, "/**").permitAll()
                                /* PO stock-in nav alerts: TESTING / ADMIN / STOCK (no @PreAuthorize on Kotlin controllers). */
                                .requestMatchers(HttpMethod.GET, "/stockInLine/alerts/purchase-incomplete-count")
                                        .hasAnyAuthority("TESTING", "ADMIN", "STOCK")
                                .requestMatchers(HttpMethod.GET, "/stockInLine/alerts/purchase-incomplete")
                                        .hasAnyAuthority("TESTING", "ADMIN", "STOCK")
                                .requestMatchers(HttpMethod.GET, "/product-process/Demo/Process/alerts/fg-qc-putaway")
                                        .hasAuthority("TESTING")
                                .requestMatchers(HttpMethod.GET, "/device-presence/ping").authenticated()
                                .requestMatchers(HttpMethod.POST, "/device-presence/heartbeat").authenticated()
                                .requestMatchers(HttpMethod.GET, "/device-presence/active")
                                        .hasAnyAuthority("TESTING", "ADMIN")
                                .requestMatchers(HttpMethod.GET, "/device-presence/history")
                                        .hasAnyAuthority("TESTING", "ADMIN")
                                .requestMatchers(HttpMethod.GET, "/printer-monitor/status")
                                        .hasAnyAuthority("TESTING", "ADMIN")
                                .requestMatchers(HttpMethod.GET, "/printer-monitor/history")
                                        .hasAnyAuthority("TESTING", "ADMIN")
                                .requestMatchers(HttpMethod.POST, "/printer-monitor/check")
                                        .hasAnyAuthority("TESTING", "ADMIN")
                                .requestMatchers(HttpMethod.GET, "/label-printer-monitor/status")
                                        .hasAnyAuthority("TESTING", "ADMIN")
                                .requestMatchers(HttpMethod.POST, "/label-printer-monitor/check")
                                        .hasAnyAuthority("TESTING", "ADMIN")
                                .requestMatchers(HttpMethod.GET, "/label-printer-monitor/label-stats")
                                        .hasAnyAuthority("TESTING", "ADMIN")
                                .requestMatchers(HttpMethod.GET, "/label-printer-monitor/odometer-stats")
                                        .hasAnyAuthority("TESTING", "ADMIN")
                                .requestMatchers(HttpMethod.POST, "/stockAdjustment/submit")
                                        .hasAnyAuthority("ADMIN", "INVENTORY_ADJUST")
                                .requestMatchers(HttpMethod.GET, "/inventoryLotLine/trace")
                                        .hasAuthority("ITEM_TRACING")
                                .requestMatchers(HttpMethod.GET, "/inventoryLotLine/trace/location/**")
                                        .hasAuthority("ITEM_TRACING")
                                .anyRequest().authenticated())
                .httpBasic(httpBasic -> httpBasic.authenticationEntryPoint(
                        (request, response, authException) -> sendUnauthorizedJson(response, "Unauthorized", "UNAUTHORIZED")))
                .sessionManagement(
                        sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }

    /** Send 401 with JSON body so frontend can consistently handle session timeout / missing token. */
    private static void sendUnauthorizedJson(HttpServletResponse response, String message, String code) throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        String body = String.format("{\"message\":\"%s\",\"code\":\"%s\"}", message.replace("\"", "\\\""), code);
        response.getWriter().write(body);
    }
}
