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.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.SecurityFilterChain;

import jakarta.servlet.DispatcherType;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;

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

	// Konfiguracja właściwa dla SprSec >= 6.1.2 na podstawie
	// https://github.com/spring-projects/spring-security/issues/13568
	// błąd opisany https://spring.io/security/cve-2023-34035
	@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(mvc.pattern("/")).permitAll()
					.requestMatchers(mvc.pattern("/whoami")).permitAll()
					.requestMatchers(mvc.pattern("/*.css")).permitAll()
	                .requestMatchers(mvc.pattern("/hello"), mvc.pattern("/time")).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/**")).permitAll() // 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()
	                .anyRequest().denyAll() // dobra praktyka - odrzucanie pozostałych zapytań; Spring domyślnie wymagałby "authenticated"
				)
			.formLogin(Customizer.withDefaults()) // albo .httpBasic(Customizer.withDefaults())
//			.csrf(authz -> authz.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;
}
