package ru.yandex.webmaster.common;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourceArrayPropertyEditor;
import org.springframework.core.io.support.ResourcePropertySource;
import ru.yandex.kungfu.application.Application;
import ru.yandex.kungfu.application.ApplicationResources;
import ru.yandex.webmaster.common.security.SecretsDirPropertySource;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

/**
 * Copy-paste from kungfu SpringApplication
 * @author avhaliullin
 */
public abstract class WebmasterApplication<T extends ApplicationResources> extends Application<T> {
    private static final Logger log = LoggerFactory.getLogger(WebmasterApplication.class);

    private static final String APPLICATION_OVERRIDE_PROPERTIES = "application.override.properties";
    private static final String PROPERTIES_EXTENSION = ".properties";
    private static final String SECRETS_DIR_PROPERTY = "yandex.secrets.dir";

    @Override
    protected void doMain(String[] args) throws Exception {
        GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext();
        applicationContext.load(getContextXmls());

        String[] pspcNames = applicationContext.getBeanNamesForType(PropertySourcesPlaceholderConfigurer.class);
        ResourceArrayPropertyEditor resourceArrayPropertyEditor = new ResourceArrayPropertyEditor();

        List<Resource> propertyLocations = new ArrayList<>();

        for (String beanName : pspcNames) {
            BeanDefinition beanDefinition = applicationContext.getBeanDefinition(beanName);
            PropertyValue locations = beanDefinition.getPropertyValues().getPropertyValue("locations");

            resourceArrayPropertyEditor.setValue(locations.getValue());
            Resource[] locationResources = (Resource[]) resourceArrayPropertyEditor.getValue();

            propertyLocations.addAll(Arrays.asList(locationResources));
        }

        List<Resource> modifiedProperties = new ArrayList<>();
        for (Resource propertyLocation : propertyLocations) {
            modifiedProperties.add(propertyLocation);

            String propertyFileName = propertyLocation.getFilename();
            int extensionStart = propertyFileName.indexOf(PROPERTIES_EXTENSION);
            if (extensionStart > 0) {
                String environmentPropertyFileName =
                        propertyFileName.substring(0, extensionStart) + "." + getEnvironment() + PROPERTIES_EXTENSION;
                try {
                    Resource environmentPropertiesLocation = propertyLocation.createRelative(environmentPropertyFileName);
                    if (environmentPropertiesLocation.exists()) {
                        modifiedProperties.add(environmentPropertiesLocation);
                    }
                } catch (IOException e) {
                    // Ignore - environment specific resource does not exist
                }
            }
        }


        Resource globalOverrideProperties = getApplicationResources().getResourceInConfig(APPLICATION_OVERRIDE_PROPERTIES);
        if (globalOverrideProperties.exists()) {
            log.warn("Found global overrides: " + globalOverrideProperties.getFile().getAbsolutePath());
            modifiedProperties.add(globalOverrideProperties);
        }

        for (Resource resource : modifiedProperties) {
            log.info("Found properties: " + resource);
        }

        for (String beanName : pspcNames) {
            applicationContext.removeBeanDefinition(beanName);
        }

        MutablePropertySources propertySources = new MutablePropertySources();
        for (Resource propertyResource : modifiedProperties) {
            try {
                propertySources.addFirst(new ResourcePropertySource(propertyResource));
            } catch (IOException e) {
                throw new RuntimeException("Unable to load properties", e);
            }
        }

        String localSecretPropertiesDir = getApplicationResources().getConfig().getFilename() + "/secrets";
        log.info("Load local secrets from dir {}", localSecretPropertiesDir);
        propertySources.addLast(new SecretsDirPropertySource(localSecretPropertiesDir));

        String secretPropertiesDir = System.getProperty(SECRETS_DIR_PROPERTY);
        if (secretPropertiesDir == null) {
            secretPropertiesDir = "/etc/yandex/" + getApplicationInfo().getProject() + "/secrets";
        }
        log.info("Load global secrets from {}", secretPropertiesDir);
        propertySources.addLast(new SecretsDirPropertySource(secretPropertiesDir));

        Properties applicationProperties = createApplicationProperties();
        propertySources.addFirst(new PropertiesPropertySource("application-properties", applicationProperties));
        PropertySourcesPlaceholderConfigurer propertiesConfigurer = new PropertySourcesPlaceholderConfigurer();
        propertiesConfigurer.setPropertySources(propertySources);

        applicationContext.getBeanFactory().registerSingleton("application-environment-properties", propertiesConfigurer);
        applicationContext.getBeanFactory().registerSingleton("application.resources", getApplicationResources());
        applicationContext.getBeanFactory().registerSingleton("application.info", getApplicationInfo());

        applicationContext.registerShutdownHook();

        try {
            applicationContext.refresh();
            applicationContext.start();
        } catch (Exception e) {
            LoggerFactory.getLogger(WebmasterApplication.class).error("Unable to start spring context", e);
            applicationContext.stop();
            applicationContext.close();
            throw e;
        }
    }

    private Properties createApplicationProperties() throws IOException {
        Properties properties = new Properties();
        properties.setProperty("application.project", getApplicationInfo().getProject());
        properties.setProperty("application.module", getApplicationInfo().getModule());
        properties.setProperty("application.version", getApplicationInfo().getVersion());
        properties.setProperty("application.buildDate", getApplicationInfo().getBuildDate());
        properties.setProperty("application.hostname", System.getProperty(APPLICATION_STARTUP_HOSTNAME));
        properties.setProperty("application.http.port", String.valueOf(getApplicationResources().getPort()));
        properties.setProperty("application.folders.config", getApplicationResources().getConfig().getFile().getAbsolutePath());
        properties.setProperty("application.folders.data", getApplicationResources().getData().getFile().getAbsolutePath());
        properties.setProperty("application.folders.temp", getApplicationResources().getTemp().getFile().getAbsolutePath());
        return properties;
    }

    protected abstract String[] getContextXmls();
}
