package sklep.security;

import jakarta.servlet.DispatcherType;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import org.springframework.security.web.SecurityFilterChain;

import javax.sql.DataSource;

// https://docs.spring.io/spring-security/reference/servlet/getting-started.html

// 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
@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain configHttpSecurity(HttpSecurity httpSecurity, HandlerMappingIntrospector hmi) throws Exception {
        MvcRequestMatcher.Builder mvc = new MvcRequestMatcher.Builder(hmi);
        httpSecurity
                .authorizeHttpRequests(authz -> authz
                        // zezwalamy na działanie przekierowań wewnętrznych (szablony) i błędów
                        .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
                        .requestMatchers("/", "/hello", "/czas", "/skrypt").permitAll()
                        .requestMatchers(mvc.pattern("/whoami")).permitAll()
                        .requestMatchers("/*.css", "/*.js").permitAll()
                        .requestMatchers(mvc.pattern("/alt?/**")).authenticated() // zalogowany jako ktokolwiek
                        // kolejność reguł ma znaczenie - pierwsza reguła, do której pasuje zapytanie, jest decydująca
                        // np. /products/new wymaga uprawnień managera, chociaż to zapytanie pasowałoby te z do /products/**
                        .requestMatchers(mvc.pattern("/products/new"), mvc.pattern("/products/*/edit")).hasAuthority("ROLE_manager")
                        .requestMatchers(mvc.pattern("/products/szukaj")).authenticated()
                        .requestMatchers(mvc.pattern("/products/**")).permitAll() // pozostałe zapytanie w obrębie products - dopuszczamy wszystkich; kolejność reguł ma znaczenie
                        .requestMatchers(mvc.pattern("/customers/new"), mvc.pattern("/customers/*/edit")).hasRole("manager") // skrót na hasAuthority("ROLE_...")
                        .requestMatchers(mvc.pattern("/customers/**")).authenticated()
                        .requestMatchers(mvc.pattern(HttpMethod.GET, "/rest/**")).permitAll()
                        .requestMatchers(mvc.pattern(HttpMethod.POST, "/rest/**")).hasRole("manager")
                        .requestMatchers(mvc.pattern(HttpMethod.PUT, "/rest/**")).hasRole("manager")
                        .requestMatchers(mvc.pattern(HttpMethod.DELETE, "/rest/**")).hasRole("manager")
                        .anyRequest().denyAll() // dobra praktyka - odrzucanie pozostałych zapytań; Spring domyślnie wymagałby "authenticated"
                )
                .formLogin(Customizer.withDefaults())
//                .formLogin(config -> config
//                        .loginPage("/moja_strona_logowania")
//                        .usernameParameter("nazwa_uzytkownika")
//                        .passwordParameter("haslo")
//                        .failureForwardUrl("/porazka-logowania")
//                )
                // Dla zapytań restowych większy sens ma takie ustawienie:
                // .httpBasic(Customizer.withDefaults())
                // .csrf(config -> config.disable())
                .csrf(config -> config.ignoringRequestMatchers("/rest/**"))
                .cors(config -> config.disable());
        return httpSecurity.build();
    }

    // Aspektem konfiguracji, który jest podawany w innej metodzie, jest zdefiniowany zbiór użytkowników.
    // W tej wersji definiujemy użytkowników a oparciu o bazę danych SQL.
    @Bean
    JdbcUserDetailsManager userDetailsService2() {
        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;
    }

    @Autowired
    private DataSource dataSource;

}
