package sklep.security;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
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.CsrfConfigurer;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import jakarta.servlet.DispatcherType;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    private DataSource dataSource;
    
    // Metoda odpowiedzialna za ogólną konfigurację security aplikacj webowej, w tym:
    // - reguły autoryzacji, czyli kot może wykonywać jakie zapytania
    // - sposób uwierzytelniania
    //   (czy tradycyjny formularz logowania, czy HttpBasic, czy OAuth2, ...)
    
    // W tej wersji różnym rolom dajemy różne poziomy dostępu.
    // (inaczej mówiąc: dla róznych adresów mamy różne wymagania)
    
    // To pomogło mi napisać poprawną konfigurację w "nowym stylu":
    // https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html
    // Kluczowe było dodanie linii z DispatcherType.FORWARD → bez tego Spring wymagał autentykacji na etapie przechodzenia do szablonu jsp (FORWARD) lub wyświetlenia błędu
    
    @Bean
    SecurityFilterChain configHttpSecurity(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeHttpRequests(authz -> authz
                // zezwalamy na działanie przekierowań wewnętrznych (szablony) i błędów
                .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
                .requestMatchers("/", "/whoami", "/*.css").permitAll()
                .requestMatchers("/hello", "/time").permitAll()
                .requestMatchers("/alt?/**").authenticated() // zalogowany jako ktokolwiek
                .requestMatchers("/products/new", "/products/*/edit").hasAuthority("ROLE_manager")
                .requestMatchers("/products/**").permitAll() // kolejność reguł ma znaczenie
                .requestMatchers("/customers/new", "/customers/*/edit").hasRole("manager") // skrót na hasAuthority("ROLE_...")
                .requestMatchers("/customers/**").authenticated()
                .requestMatchers("/rest/**").permitAll()
                .anyRequest().denyAll() // dobra praktyka - odrzucanie pozostałych zapytań; Spring domyślnie wymagałby "authenticated"
            ).formLogin(Customizer.withDefaults())
            .csrf(c -> c.ignoringRequestMatchers("/rest/**"))
        ;
        return httpSecurity.build();
    }
    
    // Aspektem konfiguracji, który jest podawany w innej metodzie, jest zdefiniowany zbiór użytkowników.

    @Bean
    JdbcUserDetailsManager userDetailsService2() {
        // Użytkownicy zdefiniowany w tabelach bazodanowych.
        JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource);

        // Mamy podać zapytanie SQL, które pozwoli Springowi odczytać informacje o userze na podstawie nazwy usera
        // w wyniku ma zwrócić rekord z trzeba kolumnami: nazwa, hasło, czy aktywny (0/1)
        jdbcUserDetailsManager.setUsersByUsernameQuery("SELECT username, password, enabled FROM spring_accounts WHERE username = ?");
        
        // dla użytkownika zwraca info o uprawnieniach (rolach) danego użytkownika; wynik może składać się z wielu rekordów
        jdbcUserDetailsManager.setAuthoritiesByUsernameQuery("SELECT username, role FROM spring_account_roles WHERE username = ?");
        return jdbcUserDetailsManager;
    }
    
}
