import api.ProxyResource;
import api.TasksResource;
import api.TeamsResource;
import com.bazaarvoice.dropwizard.webjars.WebJarBundle;
import com.google.common.base.Optional;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import core.ApplicationConfiguration;
import core.TaskType;
import database.MongoDBConnector;
import io.dropwizard.Application;
import io.dropwizard.assets.AssetsBundle;
import io.dropwizard.auth.*;
import io.dropwizard.auth.basic.BasicCredentialAuthFilter;
import io.dropwizard.auth.basic.BasicCredentials;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import objects.Flag;
import objects.Task;
import objects.User;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import repositories.SolutionsRepository;
import repositories.TasksRepository;
import repositories.TeamsRepository;
import repositories.UsersRepository;

import java.util.UUID;
import java.util.stream.Stream;

/**
 * Created by gpietrus on 16.02.16.
 */
public class CTFApplication extends Application<ApplicationConfiguration> {

    private UsersRepository usersRepository; //todo: refactor to injects
    private TasksRepository tasksRepository; //todo: refactor to injects

    @Override
    public void initialize(final Bootstrap<ApplicationConfiguration> bootstrap)
    {
        bootstrap.addBundle(new AssetsBundle("/assets", "/page", "index.html"));
        Stream.of("org.webjars.npm", "org.webjars.bower")
                .map(WebJarBundle::new)
                .forEach(bootstrap::addBundle);
    }

    @Override
    public void run(ApplicationConfiguration applicationConfiguration, Environment environment) throws Exception {
        MongoDBConnector dbConnector = new MongoDBConnector(applicationConfiguration);
        dbConnector.connect();
        Injector injector = createInjector(applicationConfiguration, dbConnector);

        //REGISTER TEAMS

        TeamsRepository teamsRepository = new TeamsRepository(dbConnector);
        teamsRepository.clean();
        applicationConfiguration.getTeams().forEach(teamsRepository::add);

        //GENERATE TASKS
        tasksRepository.clean();

        int numberOfNewTasks = 20;

        for (int i = 0; i < numberOfNewTasks; i++) {
            Task task = new Task(String.valueOf(UUID.randomUUID()), i,
                    i % 2 == 0 ? TaskType.CRYPTO : TaskType.WEB,
                    Flag.newRandomFlag()
            );
            tasksRepository.add(task);
        }

        //GENERATE TASKS END

        //REGISTER RESOURCES
        environment.jersey().register(injector.getInstance(TeamsResource.class));
        environment.jersey().register(injector.getInstance(TasksResource.class));
        environment.jersey().register(injector.getInstance(ProxyResource.class));

        //REGISTER AUTH
        environment.jersey().register(new AuthDynamicFeature(
                new BasicCredentialAuthFilter.Builder<User>()
                        .setAuthenticator(new ExampleAuthenticator(usersRepository))
                        .setAuthorizer(new ExampleAuthorizer())
                        .setRealm("SUPER SECRET STUFF")
                        .buildAuthFilter()));
        environment.jersey().register(RolesAllowedDynamicFeature.class);
        //If you want to use @Auth to inject a custom Principal type into your resource
        environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
    }

    //todo: move to seperate class
    public class ExampleAuthenticator implements Authenticator<BasicCredentials, User> {

        private UsersRepository usersRepository;

        public ExampleAuthenticator(UsersRepository usersRepository) {
            this.usersRepository = usersRepository;
        }

        @Override
        public com.google.common.base.Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
            java.util.Optional<User> user = usersRepository.authenticateUser(credentials);
            if (user.isPresent()) {
                return Optional.of(user.get());
            }
            return Optional.absent();
        }
    }

    public class ExampleAuthorizer implements Authorizer<User> {
        @Override
        public boolean authorize(User user, String role) {
            return user.getName().equals("good-guy") && role.equals("ADMIN");
        }

    }

    //todo: inject dbonnector
    private Injector createInjector(ApplicationConfiguration applicationConfiguration, MongoDBConnector dbConnector) {
        return Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bind(ApplicationConfiguration.class).toInstance(applicationConfiguration);
                bind(MongoDBConnector.class).toInstance(dbConnector);

                TeamsRepository teamsRepository = new TeamsRepository(dbConnector);
                usersRepository = new UsersRepository(dbConnector, teamsRepository);
                SolutionsRepository solutionsRepository = new SolutionsRepository(dbConnector);
                tasksRepository = new TasksRepository(applicationConfiguration, dbConnector, teamsRepository, solutionsRepository);

                bind(TeamsRepository.class).toInstance(teamsRepository);
                bind(TasksRepository.class).toInstance(tasksRepository);
                bind(UsersRepository.class).toInstance(usersRepository);

                bind(TeamsResource.class).toInstance(new TeamsResource(teamsRepository));
                bind(TasksResource.class).toInstance(new TasksResource(tasksRepository
                ));
                bind(ProxyResource.class).toInstance(new ProxyResource());

            }
        });
    }

    public static void main(String[] args) throws Exception {
        //todo: add mongo-java-driver as maven dep
        //todo: add dropwizard-auth as maven dep
        new CTFApplication().run(args);
    }
}
