/*
 * Copyright 2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.bearchoke.platform.server.frontend.web.config;

import com.bearchoke.platform.domain.search.repository.LocationRepository;
import com.bearchoke.platform.server.common.security.ApiAuthenticationFailureHandler;
import com.bearchoke.platform.server.common.security.ApiAuthenticationSuccessHandler;
import com.bearchoke.platform.server.frontend.service.GreetingService;
import com.bearchoke.platform.server.frontend.service.GreetingServiceImpl;
import com.bearchoke.platform.domain.user.document.Role;
import com.bearchoke.platform.domain.user.document.User;
import com.bearchoke.platform.domain.user.repositories.UserRepository;
import com.bearchoke.platform.domain.user.security.PreAuthUserDetailsService;
import com.bearchoke.platform.domain.user.security.PreAuthenticatedTokenCacheService;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.log4j.Log4j2;
import org.axonframework.commandhandling.CommandBus;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.geo.GeoJsonModule;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.messaging.core.MessageSendingOperations;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.zalando.jackson.datatype.money.MoneyModule;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.mock;

/**
 * Created by Bjorn Harvold
 * <p>
 * Date: 1/3/14
 * <p>
 * Time: 4:12 PM
 * <p>
 * Responsibility:
 */

@Configuration
@Log4j2
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class MockServerConfig extends GlobalMethodSecurityConfiguration {

    @Bean(name = "customObjectMapper")
    public ObjectMapper customObjectMapper() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();

        builder.featuresToDisable(
                SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
        );

        builder.featuresToEnable(
                SerializationFeature.WRITE_DATES_WITH_ZONE_ID,
//                SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
//                SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS,
//                DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS,
                DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT
        );

        builder.indentOutput(true);

        builder.failOnEmptyBeans(false);
        builder.failOnUnknownProperties(false);

        // do not include null value in json to make object graph smaller
        builder.serializationInclusion(JsonInclude.Include.NON_NULL);

        builder.modules(new GeoJsonModule(), new JavaTimeModule(), new MoneyModule());

        return builder.build();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
        builder.authenticationProvider(authenticationProvider());
    }

    @Bean(name = "commandBus")
    public CommandBus commandBus() {
        return mock(CommandBus.class);
    }

    @Bean(name = "preAuthenticatedTokenCacheService")
    public PreAuthenticatedTokenCacheService preAuthenticatedTokenCacheService() {
        return mock(PreAuthenticatedTokenCacheService.class);
    }

    @Bean(name = "cacheManager")
    public CacheManager cacheManager() {
        return mock(CacheManager.class);
    }

    @Bean(name = "redisTemplate")
    public RedisTemplate redisTemplate() {
        return mock(RedisTemplate.class);
    }

    @Bean(name = "userDetailsService")
    public UserDetailsService inMemoryUserDetailsManager() {
        List<UserDetails> users = new ArrayList<>(1);
        users.add(new User("user", "[email protected]", "User", "User", "user", Arrays.asList(new Role("ROLE_USER", Arrays.asList("RIGHT_AS_USER")))));
        users.add(new User("[email protected]", "[email protected]", "Facebook", "User", "facebook", Arrays.asList(new Role("ROLE_USER", Arrays.asList("RIGHT_AS_USER")))));

        return new InMemoryUserDetailsManager(users);
    }

    @Bean(name = "authenticationProvider")
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider result = new DaoAuthenticationProvider();
        result.setUserDetailsService(inMemoryUserDetailsManager());

        return result;
    }

    @Bean(name = "apiAuthenticationSuccessHandler")
    public ApiAuthenticationSuccessHandler apiAuthenticationSuccessHandler() {
        return new ApiAuthenticationSuccessHandler(preAuthenticatedTokenCacheService());
    }

    @Bean(name = "apiAuthenticationFailureHandler")
    public ApiAuthenticationFailureHandler apiAuthenticationFailureHandler() {
        return new ApiAuthenticationFailureHandler(customObjectMapper());
    }

    @Bean(name = "greetingService")
    public GreetingService greetingService() {
        return new GreetingServiceImpl();
    }

    @Bean(name = "userRepository")
    public UserRepository userRepository() {
        return mock(UserRepository.class);
    }

    @Bean(name = "locationRepository")
    public LocationRepository locationRepository() {
        return mock(LocationRepository.class);
    }

    @Bean(name = "messageSendingOperations")
    public MessageSendingOperations messageSendingOperations() {
        return mock(MessageSendingOperations.class);
    }

    @Bean(name = "springSessionRepositoryFilter")
    public SessionRepositoryFilter sessionRepositoryFilter() {
        return new SessionRepositoryFilter(new MapSessionRepository());
    }

    @Bean(name = "preAuthUserDetailsService")
    public PreAuthUserDetailsService preAuthUserDetailsService() {
        PreAuthUserDetailsService result = mock(PreAuthUserDetailsService.class);

        // set this bean to never return anything of value
        given(result.loadUserDetails(anyObject())).willThrow(new UsernameNotFoundException("Pre authenticated token not found"));

        return result;
    }

    @Bean(name = "preAuthAuthenticationManager")
    public AuthenticationManager preAuthAuthenticationManager() {
        PreAuthenticatedAuthenticationProvider preAuthProvider = new PreAuthenticatedAuthenticationProvider();
        preAuthProvider.setPreAuthenticatedUserDetailsService(preAuthUserDetailsService());

        return new ProviderManager(Arrays.asList(preAuthProvider));
    }


}