package ite.librarymaster.application.configuration;

import com.fasterxml.jackson.databind.node.ObjectNode;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.security.core.GrantedAuthority;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableMap;

import static org.springframework.security.config.Customizer.withDefaults;


@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity  http, @Qualifier("opaWebClient") WebClient opaWebClient) throws Exception {

        // TODO-6: Plug-in Authorization manager for /library/** path
        http
            .authorizeExchange((authz) -> authz
                   .anyExchange().permitAll()
            ).httpBasic(withDefaults());
               
        return http.build();
    }

    @Bean
    public MapReactiveUserDetailsService userDetailsService() {
        UserDetails admin = User.withUsername("admin")
                .password("{noop}admin")
                .roles("ADMIN", "USER" ).build();
        UserDetails  user = User.withUsername("user")
                .password(passwordEncoder().encode("user"))
                .roles("USER").build();

        return new MapReactiveUserDetailsService(admin,user);
    }

    //  This makes sure that the passwords are hashed
    //  (by default using the `bcrypt' hashing algorithm).
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    // TODO-5: Add implementation of _ReactiveAuthorizationManager_ Bean


    // TODO-4: Add implementation of decision function

    private Publisher<Map<String, Object>> toAuthorizationPayload(Mono<Authentication> auth, AuthorizationContext context) {
        return auth.defaultIfEmpty(
                        new AnonymousAuthenticationToken("**ANONYMOUS**", new Object(), Collections.singletonList(new SimpleGrantedAuthority("ANONYMOUS"))))
                .map(a -> {
                    Map<String, String> headers = context.getExchange()
                            .getRequest()
                            .getHeaders()
                            .toSingleValueMap();

                    Map<String, Object> attributes = ImmutableMap.<String, Object> builder()
                            .put("principal", a.getName())
                            .put("authorities", a.getAuthorities()
                                    .stream()
                                    .map(GrantedAuthority::getAuthority)
                                    .collect(Collectors.toList()))
                            .put("uri", context.getExchange()
                                    .getRequest()
                                    .getURI()
                                    .getPath())
                            .put("headers", headers)
                            .build();

                    return ImmutableMap.<String, Object> builder()
                            .put("input", attributes)
                            .build();
                });
    }

}
