package ru.yandex.qe.bus.factories;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.annotation.Nonnull;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.NamedBean;
import org.springframework.beans.factory.SmartFactoryBean;

import ru.yandex.qe.bus.features.version.Version;
import ru.yandex.qe.bus.features.version.VersionFeature;
import ru.yandex.qe.bus.features.version.VersionService;

import static ru.yandex.qe.bus.features.version.VersionFeature.PackageInfo;
import static ru.yandex.qe.bus.features.version.VersionFeature.configureVersion;
import static ru.yandex.qe.bus.features.version.VersionFeature.findApiVersion;
import static ru.yandex.qe.bus.features.version.VersionFeature.findBusVersion;
import static ru.yandex.qe.bus.features.version.VersionFeature.findServerVersion;

public class BusServerBuilderBean extends BusEndpointBuilder implements NamedBean, BeanNameAware, SmartFactoryBean<Server> {

    private final static Logger LOG = LoggerFactory.getLogger(BusServerBuilderBean.class);

    private List<Object> services = Collections.emptyList();

    private List<Object> builtinServices = Collections.emptyList();

    private List<ResourceProvider> resourceProviders = Collections.emptyList();

    private Map<Object, Object> extensionMappings;

    private String name;

    private String hostName;

    @Override
    public String getBeanName() {
        return name;
    }

    @Override
    public void setBeanName(String name) {
        this.name = name;
    }

    public void setHostName(final String hostName) {
        this.hostName = hostName;
    }

    public void setServices(@Nonnull List<Object> services) {
        this.services = services;
    }

    public void setService(@Nonnull Object service) {
        this.services = Collections.singletonList(service);
    }

    public void setBuiltinServices(final List<Object> builtinServices) {
        this.builtinServices = builtinServices;
    }

    public void setResources(List<ResourceProvider> resourceProviders) {
        this.resourceProviders = resourceProviders;
    }

    public void setResource(ResourceProvider resourceProvider) {
        this.resourceProviders = Collections.singletonList(resourceProvider);
    }

    public void setExtensionMappings(@Nonnull Map<Object, Object> extensionMappings) {
        this.extensionMappings = extensionMappings;
    }

    @Override
    public Server getObject() throws Exception {
        LOG.info("services: " + services);
        final JAXRSServerFactoryBean serverFactoryBean = new JAXRSServerFactoryBean();
        serverFactoryBean.setStaticSubresourceResolution(true);
        prepareFactoryBean(serverFactoryBean);

        final List<Object> autoServices = getAutoServices(serverFactoryBean);
        final List<Object> allServices = new ArrayList<>(autoServices);
        allServices.addAll(services);
        serverFactoryBean.setServiceBeans(allServices);
        serverFactoryBean.setResourceProviders(resourceProviders);
        serverFactoryBean.setExtensionMappings(extensionMappings);
        return serverFactoryBean.create();
    }

    /**
     * Auto-configured services. Descendants can override; it is recommended to join with super.getAutoServices().
     * @param serverFactoryBean
     * @return auto-configured services.
     * @throws Exception
     */
    protected List<Object> getAutoServices(final JAXRSServerFactoryBean serverFactoryBean) throws Exception {
        final List<Object> autoServices = new ArrayList<>(builtinServices);
        autoServices.add(configureVersionFeature(serverFactoryBean));
        return autoServices;
    }

    private Object configureVersionFeature(JAXRSServerFactoryBean serverFactoryBean) throws Exception {
        final String busVersion = findBusVersion();
        final List<Object> servicesWithoutBus = filterBusService(services);
        final VersionFeature.PackageInfo serverInfo = findServerVersion(servicesWithoutBus, resourceProviders);
        final String serverVersion = serverInfo.getVersion();
        final PackageInfo apiInfo = findApiVersion(servicesWithoutBus, resourceProviders);
        final String apiVersion = apiInfo.getVersion();
        final String serverGroup = serverInfo.getVendor();
        final String serverArtifact = serverInfo.getTitle();
        final String apiGroup = apiInfo.getVendor();
        final String apiArtifact = apiInfo.getTitle();
        configureVersion(serverFactoryBean, serverArtifact, serverVersion, apiArtifact, apiVersion);
        return new VersionService(new Version(serverGroup, serverArtifact, serverVersion, apiGroup, apiArtifact, apiVersion, busVersion,
                hostName, DateTime.now()));
    }

    private List<Object> filterBusService(List<Object> resources) {
       return Lists.newArrayList(Iterables.filter(resources, new Predicate<Object>() {
            @Override
            public boolean apply(Object input) {
                final Package myPackage = AopUtils.getTargetClass(input).getPackage();
                return !myPackage.getName().contains("ru.yandex.qe.bus");
            }
        }));
    }

    @Override
    public Class<?> getObjectType() {
        return Server.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public boolean isPrototype() {
        return false;
    }

    @Override
    public boolean isEagerInit() {
        return true;
    }
}