
/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.crunii.com</a>
 */
package com.crunii.micro.common.config;

import com.crunii.micro.common.constants.CommonConstants;
import com.crunii.micro.common.exception.BusinessException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * <pre>
 * 	 本地和测试环境需要api 生产环境安全原因不加载
 * </pre>
 *
 * @author 田平 create 2018年10月15日下午2:39:36
 */
@Configuration
@ConditionalOnProperty(name = "swagger.enabled", havingValue = "true")
@EnableOpenApi
public class SwaggerConfiguration {

	@Value("${spring.application.name}")
	private String appName;

	/**
	 * <pre>
	 *     可以单独指定一个特别的扫描路径，
	 *     除非特殊情况，一般不建议使用
	 * </pre>
	 */
	@Value("${swagger.special.controller.package:NONE}")
	private String specialControllerPackage;

	private AntPathMatcher matcher = new AntPathMatcher();

	private static final String BASE_PKG_PREFIX = "com.crunii.micro.";
	private static final String BASE_PKG_SUFFIX = ".controller";

	public static final Map<String, String> PACKAGE_MAPPER = new HashMap<>();

	static {
		PACKAGE_MAPPER.put("service-", "service.");
		PACKAGE_MAPPER.put("intf-", "intf.");
		PACKAGE_MAPPER.put("module-", "module.");
	}

	@Bean
	public Docket createRestApi() {
		// 设置可以扫描多个包逗号分隔
		String packageStr = getControllerPackage(appName);

		if (!specialControllerPackage.equals("NONE")) {
			packageStr = packageStr + "," + specialControllerPackage;
		}

		return new Docket(DocumentationType.OAS_30)
				.apiInfo(apiInfo())
				.select()
				.apis(basePackage(packageStr))
				.paths(PathSelectors.any())
				.build()
				.securitySchemes(securitySchemes())
				.securityContexts(securityContexts());
	}

	private static String replaceLast(String text, String regex, String replacement) {
		return text.replaceFirst("(?s)" + regex + "(?!.*?" + regex + ")", replacement);
	}

	public static void main(String[] args) {
		System.out.println(getControllerPackage("service-sysmgr"));
		System.out.println(getControllerPackage("intf-hlra-ws"));
		System.out.println(getControllerPackage("module-log-http"));
	}

	private static String getControllerPackage(String appName) {
		Assert.notNull(appName, "appName not allowed null!");
		String lower = appName.toLowerCase();
		String[] suffixes = lower.split("-");
		int length = suffixes.length;
		if (length == 1) {
			return BASE_PKG_PREFIX + lower + BASE_PKG_SUFFIX;
		} else {
			StringBuilder pkgs = new StringBuilder(BASE_PKG_PREFIX);
			pkgs.append(getPackage(lower));
			List<String> subs = new ArrayList<>();
			for (int i = 1; i < length; i++) {
				subs.add(suffixes[i]);
				pkgs.append(suffixes[i]);
			}
			pkgs.append(BASE_PKG_SUFFIX);
			String subsStr = subs.stream().collect(Collectors.joining("."));
			while (subsStr.indexOf(".") > -1) {
				StringBuilder sb = new StringBuilder(",");
				sb.append(BASE_PKG_PREFIX);
				sb.append(getPackage(lower));
				sb.append(subsStr);
				sb.append(BASE_PKG_SUFFIX);
				pkgs.append(sb);
				subsStr = replaceLast(subsStr.toString(), "\\.", "");
			}
			return pkgs.toString();
		}
	}

	public static String getPackage(String lower) {
		for (Map.Entry<String, String> entry : PACKAGE_MAPPER.entrySet()) {
			if (lower.startsWith(entry.getKey())) {
				return entry.getValue();
			}
		}
		throw new BusinessException("unsupported the project name prefix:{},please config in SwaggerConfig.PACKAGE_MAPPER", lower);
	}

	public static Predicate<RequestHandler> basePackage(final String basePackage) {
		return new Predicate<RequestHandler>() {
			@Override
			public boolean test(RequestHandler t) {
				return declaringClass(t).map(handlerPackage(basePackage)).orElse(true);
			}
		};
	}

	private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {
		return input -> {
			for (String strPackage : basePackage.split(",")) {
				boolean isMatch = input.getPackage().getName().startsWith(strPackage);
				if (isMatch) {
					return true;
				}
			}
			return false;
		};
	}

	@SuppressWarnings("deprecation")
	private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
		return Optional.ofNullable(input.declaringClass());
	}

	private ApiInfo apiInfo() {
		return new ApiInfoBuilder().title(appName + " Rest Api").version("1.0").build();
	}

	private List<SecurityScheme> securitySchemes() {
		List<SecurityScheme> schemes = new ArrayList<>();
		schemes.add(new ApiKey("Authorization", "token", "header"));
		return schemes;
	}

	private List<SecurityContext> securityContexts() {
		return Arrays.asList(SecurityContext.builder()
				.securityReferences(defaultAuth())
				.operationSelector(ctx -> matcher.match(CommonConstants.AUTH_PATTERN, ctx.requestMappingPattern()))
				.build());
	}

	private List<SecurityReference> defaultAuth() {
		AuthorizationScope[] authorizationScopes = { new AuthorizationScope("global", "accessEverything") };
		return Arrays.asList(new SecurityReference("Authorization", authorizationScopes));
	}

}
