package com.atlassian.stash.internal.plugin;

import com.atlassian.bitbucket.hook.repository.*;
import com.atlassian.plugin.ModuleDescriptor;
import com.atlassian.plugin.Plugin;
import com.atlassian.plugin.PluginParseException;
import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
import com.atlassian.plugin.hostcontainer.HostContainer;
import com.atlassian.plugin.module.ModuleFactory;
import com.atlassian.plugin.osgi.factory.OsgiPlugin;
import com.atlassian.plugin.util.validation.ValidationPattern;
import com.atlassian.plugin.web.WebFragmentHelper;
import com.atlassian.plugin.web.descriptors.ConditionElementParser;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.stash.internal.plugin.web.fragments.ClientWebInterfaceManager;
import com.atlassian.stash.internal.plugin.web.fragments.ConfigurationFormModuleDescriptor;
import com.atlassian.bitbucket.setting.RepositorySettingsValidator;
import org.dom4j.Element;
import org.osgi.framework.ServiceRegistration;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static com.atlassian.plugin.util.validation.ValidationPattern.test;

public class RepositoryHookModuleDescriptor extends AbstractModuleDescriptor<RepositoryHookImplementor> implements ValidatorModuleDescriptor<RepositoryHookImplementor> {

    public static final String XML_ELEMENT_NAME = "repository-hook";

    private final ClientWebInterfaceManager clientWebInterfaceManager;
    private final ConditionElementParser.ConditionFactory conditionFactory;
    private final HostContainer hostContainer;
    private final I18nService i18nService;
    private final WebResourceGenerationHelper webResourceGenerationHelper;
    private final WebFragmentHelper webFragmentHelper;

    private String iconPath;

    private ConfigurationFormModuleDescriptor configuredFormModuleDescriptor;
    private ServiceRegistration configurationFormRegistration;

    private RepositoryHookType type;
    private RepositoryHookImplementor module;
    private String validatorClassName;
    private RepositorySettingsValidator validator;

    public RepositoryHookModuleDescriptor(ModuleFactory moduleFactory,
                                          ClientWebInterfaceManager clientWebInterfaceManager,
                                          ConditionElementParser.ConditionFactory conditionFactory,
                                          WebFragmentHelper webFragmentHelper,
                                          HostContainer hostContainer,
                                          I18nService i18nService,
                                          WebResourceGenerationHelper webResourceGenerationHelper) {
        super(moduleFactory);
        this.clientWebInterfaceManager = clientWebInterfaceManager;
        this.conditionFactory = conditionFactory;
        this.i18nService = i18nService;
        this.hostContainer = hostContainer;
        this.webResourceGenerationHelper = webResourceGenerationHelper;
        this.webFragmentHelper = webFragmentHelper;
    }

    @Override
    protected void provideValidationRules(ValidationPattern pattern) {
        super.provideValidationRules(pattern);
        pattern.rule(
                test("@class").withError("The class for the hook implementation is required"),
                test("@name").withError("The name is required")
        );
    }

    @Override
    public void init(@Nonnull Plugin plugin, @Nonnull Element element) throws PluginParseException {
        super.init(plugin, element);
        Element iconElement = element.element("icon");
        if (iconElement != null) {
            iconPath = iconElement.getTextTrim();
        }
        Element configElement = element.element(ConfigurationFormModuleDescriptor.XML_ELEMENT_NAME);

        if (configElement != null) {
            if (!(plugin instanceof OsgiPlugin)) {
                throw new PluginParseException(XML_ELEMENT_NAME + " can only be used in OSGi plugins");
            }
            configElement = processConfig(element, configElement);
            configuredFormModuleDescriptor = new ConfigurationFormModuleDescriptor(moduleFactory, hostContainer,
                    clientWebInterfaceManager, conditionFactory, webFragmentHelper, webResourceGenerationHelper);
            configuredFormModuleDescriptor.init(plugin, configElement);
        }

        Element validatorElement = element.element("validator");
        if (validatorElement != null) {
            validatorClassName = validatorElement.getStringValue();
        }
    }

    private Element processConfig(Element moduleElement, Element configElement) {
        configElement = configElement.createCopy();
        configElement.setParent(moduleElement.getParent());
        configElement.addAttribute("key", getKey() + "-config-form");
        return configElement;
    }

    @Override
    public void enabled() {
        super.enabled();
        Class<? extends RepositoryHookImplementor> implemented = null;
        if (PreReceiveRepositoryHook.class.isAssignableFrom(moduleClass)) {
            implemented = PreReceiveRepositoryHook.class;
            type = RepositoryHookType.PRE_RECEIVE;
        } else if (RepositoryMergeRequestCheck.class.isAssignableFrom(moduleClass)) {
            implemented = RepositoryMergeRequestCheck.class;
            type = RepositoryHookType.PRE_RECEIVE;
        }

        if (AsyncPostReceiveRepositoryHook.class.isAssignableFrom(moduleClass)) {
            if (implemented != null) {
                throw new PluginParseException(String.format(
                        "Hook must not implement both %s and %s",
                        implemented.getSimpleName(),
                        AsyncPostReceiveRepositoryHook.class
                ));
            }
            implemented = AsyncPostReceiveRepositoryHook.class;
            type = RepositoryHookType.POST_RECEIVE;
        }

        if (implemented == null) {
            throw new PluginParseException(String.format(
                    "Hook must implement %s and/or %s or %s.",
                    PreReceiveRepositoryHook.class.getSimpleName(),
                    RepositoryMergeRequestCheck.class.getSimpleName(),
                    AsyncPostReceiveRepositoryHook.class.getSimpleName()
            ));
        }

        if (configuredFormModuleDescriptor != null) {
            // register configuration form with the OSGi bundle context
            configurationFormRegistration = ((OsgiPlugin) getPlugin())
                    .getBundle()
                    .getBundleContext()
                    .registerService(ModuleDescriptor.class.getName(), configuredFormModuleDescriptor, null);
        }

        module = moduleFactory.createModule(moduleClassName, this);

        validator = createValidator();
    }

    private RepositorySettingsValidator createValidator() {
        if (validatorClassName != null) {
            try {
                Class<?> validatorClass = plugin.loadClass(validatorClassName, this.getClass());
                // Can assume this is an OsgiPlugin
                return (RepositorySettingsValidator) ((OsgiPlugin) getPlugin()).getContainerAccessor().createBean(validatorClass);
            } catch (ClassNotFoundException e) {
                throw new PluginParseException(String.format("Hook validator class '%s' not found", validatorClassName), e);
            }
        } else if (RepositorySettingsValidator.class.isAssignableFrom(moduleClass)) {
            return (RepositorySettingsValidator) getModule();
        } else {
            return null;
        }
    }

    @Override
    public void disabled() {
        type = null;

        // Ensure we unregister any modules when we disable this module
        if (configurationFormRegistration != null) {
            configurationFormRegistration.unregister();
            configurationFormRegistration = null;
        }

        module = null;
        validator = null;

        super.disabled();
    }

    @Override
    public RepositoryHookImplementor getModule() {
        return module;
    }

    public String getI18nName() {
        return getI18nNameKey() != null ? i18nService.getText(getI18nNameKey(), getName()) : getName();
    }

    @Nullable
    public String getI18nDescription() {
        return getDescriptionKey() != null ? i18nService.getText(getDescriptionKey(), getDescription()) : getDescription();
    }

    public String getIconPath() {
        return iconPath;
    }

    @Nullable
    public String getConfigFormKey() {
        return configuredFormModuleDescriptor != null ? configuredFormModuleDescriptor.getCompleteKey() : null;
    }

    public RepositoryHookType getType() {
        return type;
    }

    @Nullable
    @Override
    public RepositorySettingsValidator getValidator() {
        return validator;
    }
}
