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 com.mongodb.MongoClient;
import core.ApplicationConfiguration;
import core.TaskType;
import helpers.CryptoTaskConfig;
import helpers.WebTaskConfig;
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.jersey.setup.JerseyEnvironment;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import objects.Flag;
import objects.User;
import objects.tasks.Task;
import objects.tasks.TaskCryptoContent;
import objects.tasks.TaskWebContent;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;
import repositories.SolutionsRepository;
import repositories.TasksRepository;
import repositories.TeamsRepository;
import repositories.UsersRepository;
import resources.*;

import java.util.List;
import java.util.stream.Stream;

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

    private Morphia morphia;
    private Datastore datastore;
    private UsersRepository usersRepository; //todo: refactor to injects
    private TasksRepository tasksRepository; //todo: refactor to injects
    private TeamsRepository teamsRepository;
    private SolutionsRepository solutionsRepository;
    private Injector injector;

    @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);
    }

    private void initializeMorhpia() {
        morphia = new Morphia();
        morphia.mapPackage("objects.Team");
        datastore = morphia.createDatastore(new MongoClient(), "db"); //todo: db name from conf
        datastore.ensureIndexes();
    }

    //todo: cleanup
    private void initializeTeams(ApplicationConfiguration applicationConfiguration) {
        teamsRepository.clean();
        applicationConfiguration.getTeams().forEach(team -> {
            team.getMembers().stream().forEach(user -> datastore.save(user));
            datastore.save(team);
        });
    }

    private void initializeTasks(ApplicationConfiguration applicationConfiguration) {

        List<CryptoTaskConfig> cryptoTasks = applicationConfiguration.getCryptoTasks();
        List<WebTaskConfig> webTasks = applicationConfiguration.getWebTasks();

        tasksRepository.clean();

        cryptoTasks.forEach(cryptoTaskConfig -> tasksRepository.add(new Task(
                cryptoTaskConfig.getName(),
                cryptoTaskConfig.getLevel(),
                TaskType.CRYPTO,
                new Flag(cryptoTaskConfig.getFlag()),
                new TaskCryptoContent(cryptoTaskConfig.getText())
        )));

        webTasks.forEach(webTaskConfig -> tasksRepository.add(new Task(
                webTaskConfig.getName(),
                webTaskConfig.getLevel(),
                TaskType.WEB,
                new Flag(webTaskConfig.getFlag()),
                new TaskWebContent(webTaskConfig.getUrl())
        )));
    }

    private void initializeSolutions() {
        solutionsRepository.clean();
    }

    private void registerResources(Environment environment) {
        JerseyEnvironment jersey = environment.jersey();
        jersey.register(injector.getInstance(TeamsResource.class));
        jersey.register(injector.getInstance(TasksResource.class));
        jersey.register(injector.getInstance(ProxyResource.class));
        jersey.register(injector.getInstance(SolutionsResource.class));
        jersey.register(injector.getInstance(TaskResource.class));
    }

    private void registerAuthFeatures(Environment environment) {
        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);
        environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
    }

    @Override
    public void run(ApplicationConfiguration applicationConfiguration, Environment environment) throws Exception {
        //todo: refactor
        initializeMorhpia();
        injector = createInjector(applicationConfiguration, datastore);

        initializeTeams(applicationConfiguration);
        initializeTasks(applicationConfiguration);
        initializeSolutions();
        registerResources(environment);
        registerAuthFeatures(environment);
    }

    //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 {
            User user = usersRepository.authenticateUser(credentials);
            if (user != null) {
                return Optional.of(user);
            }
            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");
        }

    }

    private Injector createInjector(ApplicationConfiguration applicationConfiguration, Datastore datastore) {
        return Guice.createInjector(new AbstractModule() {
            @Override
            protected void configure() {
                bind(ApplicationConfiguration.class).toInstance(applicationConfiguration);

                //todo: refactor?
                teamsRepository = new TeamsRepository(datastore);
                usersRepository = new UsersRepository(datastore, teamsRepository);
                solutionsRepository = new SolutionsRepository(datastore);
                tasksRepository = new TasksRepository(applicationConfiguration, datastore, teamsRepository, solutionsRepository,
                        usersRepository);

                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(SolutionsResource.class).toInstance(new SolutionsResource(solutionsRepository));
                bind(TaskResource.class).toInstance(new TaskResource(tasksRepository));
            }
        });
    }

    public static void main(String[] args) throws Exception {
        new CTFApplication().run(args);
    }
}
