package com.atlassian.stash.internal.hook;

import com.atlassian.bitbucket.hook.*;
import com.atlassian.bitbucket.repository.RefChange;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.BaseWeightedModuleDescriptor;
import com.atlassian.bitbucket.util.ModuleDescriptorUtils;
import com.atlassian.bitbucket.util.Timer;
import com.atlassian.bitbucket.util.TimerUtils;
import com.atlassian.plugin.PluginAccessor;
import com.atlassian.plugin.spring.AvailableToPlugins;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.stream.Collectors;

@AvailableToPlugins(BuiltinHookHandlerFactory.class)
@Component("builtinHookCallbackFactory")
class DefaultBuiltInHookHandlerFactory implements BuiltinHookHandlerFactory {

    private static final Logger log = LoggerFactory.getLogger(DefaultBuiltInHookHandlerFactory.class);

    private final PluginAccessor pluginAccessor;

    @Autowired
    public DefaultBuiltInHookHandlerFactory(PluginAccessor pluginAccessor) {
        this.pluginAccessor = pluginAccessor;
    }

    @Nonnull
    @Override
    public HookHandler preReceive(@Nonnull final Repository repository, @Nonnull final Collection<RefChange> refChanges) {
        return new HookHandler() {
            @Override
            @SuppressWarnings("UnusedDeclaration")
            public boolean handle(HookRequest request, HookResponse response) {
                try (Timer loopTimer = TimerUtils.start(this.getClass().getName() + "#preReceive")) {
                    for (PreReceiveHook hook : getEnabledModules(PreReceiveHookModuleDescriptor.class)) {
                        try (Timer timer = TimerUtils.start(hook.getClass().getName() + "#onReceive")) {
                            if (!hook.onReceive(repository, refChanges, response)) {
                                return false;
                            }
                        } catch (Exception e) {
                            response.err().println("Hook " + hook.getClass().getName() + " failed. Error: ");
                            response.err().println(e.getMessage());
                            log.warn("PreReceiveHook failed, push will be rejected.", e);
                            return false;
                        }
                    }
                    return true;
                }
            }
        };
    }

    @Nonnull
    @Override
    public HookHandler postReceive(@Nonnull final Repository repository, @Nonnull final Collection<RefChange> refChanges) {
        return new HookHandler() {
            @Override
            @SuppressWarnings("UnusedDeclaration")
            public boolean handle(HookRequest request, HookResponse response) {
                try (Timer loopTimer = TimerUtils.start(this.getClass().getName() + "#postReceive")) {
                    for (PostReceiveHook hook : getEnabledModules(PostReceiveHookModuleDescriptor.class)) {
                        try (Timer timer = TimerUtils.start(hook.getClass().getName() + "#onReceive")) {
                            hook.onReceive(repository, refChanges, response);
                        } catch (Exception e) {
                            // hook failed, but we should still call other hook if possible
                            log.warn("Hook {} failed.", hook.getClass().getName(), e);
                        }
                    }
                    return true;
                }
            }
        };
    }

    @VisibleForTesting
    <T, M extends BaseWeightedModuleDescriptor<T>> Collection<T> getEnabledModules(Class<M> descriptorClass) {
        // We avoid using getEnabledModulesByClass as it is not cached and can be very expensive
        return ModuleDescriptorUtils.toSortedModules(pluginAccessor.getEnabledModuleDescriptorsByClass(descriptorClass))
                .collect(Collectors.toList());
    }

}
