package ru.yandex.qe.dispenser.swagger;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

import javax.servlet.ServletConfig;

import io.swagger.models.Swagger;
import io.swagger.models.auth.SecuritySchemeDefinition;
import org.apache.cxf.Bus;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.feature.Feature;
import org.apache.cxf.jaxrs.swagger.Swagger2Customizer;
import org.apache.cxf.jaxrs.swagger.Swagger2Feature;
import org.apache.cxf.jaxrs.swagger.ui.SwaggerUiConfig;
import org.apache.cxf.transport.http.asyncclient.AsyncHTTPConduit;
import org.apache.cxf.transport.servlet.CXFNonSpringServlet;
import org.springframework.context.ApplicationContext;
import ru.yandex.qe.bus.factories.BusEndpointBuilder;

import static org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext;

public abstract class SwaggerServlet extends CXFNonSpringServlet {

    private final String serverBeanId;
    private final String resourcePackage;
    private final String authorizationSchemeName;
    private final SecuritySchemeDefinition securitySchemeDefinition;

    /**
     * @param serverBeanId             id сервер-бина из webservice-ctx.xml, например "busServer"
     * @param resourcePackage          пакет в котором находятся ручки, на которые надо сгенерировтаь автодокументацию
     *                                 например "ru.yandex.qe.dispenser.ws"
     *                                 (правильное значение можно посмотреть в webservice-ctx.xml, в тэге context:component-scan
     * @param authorizationSchemeName  название авторизационной схемы. То же самое значение должно быть указано в
     *                                 аннотации @Authorization(value = <ВОТ ТУТ>) у каждой из ручек, иначе swagger
     *                                 не понимает, что при вызове надо использовать авторизацию
     * @param securitySchemeDefinition обьект, описывающий способ авторизации
     */
    public SwaggerServlet(final String serverBeanId, final String resourcePackage,
                          final String authorizationSchemeName, final SecuritySchemeDefinition securitySchemeDefinition) {
        this.serverBeanId = serverBeanId;
        this.resourcePackage = resourcePackage;
        this.authorizationSchemeName = authorizationSchemeName;
        this.securitySchemeDefinition = securitySchemeDefinition;
    }

    @Override
    protected void loadBus(final ServletConfig servletConfig) {
        final ApplicationContext ctx = getWebApplicationContext(servletConfig.getServletContext());
        final Server server = ctx.getBean(serverBeanId, Server.class);
        final BusEndpointBuilder busEndpointBuilder = ctx.getBean('&' + serverBeanId, BusEndpointBuilder.class);
        final Bus bus = busEndpointBuilder.getBus();
        String clusterPrefix = ctx.getEnvironment().getProperty("dispenser.cluster.prefix", "");

        final Swagger2Feature swagger2Feature = new Swagger2Feature();

        swagger2Feature.setSupportSwaggerUi(true);
        swagger2Feature.setPrettyPrint(true);
        swagger2Feature.setResourcePackage(resourcePackage);
        swagger2Feature.setUsePathBasedConfig(true);
        swagger2Feature.setSecurityDefinitions(Collections.singletonMap(authorizationSchemeName, securitySchemeDefinition));
        swagger2Feature.setCustomizer(new Swagger2Customizer() {
            @Override
            public Swagger customize(Swagger data) {
                Swagger customized = super.customize(data);
                if ("/api".equals(customized.getBasePath()) && !clusterPrefix.isEmpty()) {
                    customized.setBasePath(clusterPrefix + "/api");
                }
                return customized;
            }
        });
        SwaggerUiConfig uiConfig = new SwaggerUiConfig();
        uiConfig.setUrl(clusterPrefix + "/api/swagger.json#/");
        swagger2Feature.setSwaggerUiConfig(uiConfig);

        final Collection<Feature> features = new ArrayList<>(bus.getFeatures());
        features.add(swagger2Feature);

        bus.setProperty(AsyncHTTPConduit.USE_ASYNC, Boolean.TRUE);
        bus.setFeatures(features);

        swagger2Feature.initialize(server, bus);
        setBus(bus);
    }
}
