package frodez.config.swagger.plugin;

import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;

import com.google.common.base.Optional;
import frodez.config.swagger.SwaggerProperties;
import frodez.util.common.StrUtil;
import frodez.util.reflect.ReflectUtil;
import io.swagger.annotations.Api;
import java.util.Set;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.core.config.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.ApiListingBuilder;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ApiListingBuilderPlugin;
import springfox.documentation.spi.service.contexts.ApiListingContext;
import springfox.documentation.swagger.common.SwaggerPluginSupport;

/**
 * 自动将@RequestMapping中的属性转换为接口属性(用于类)<br>
 * 本插件使用spring注解中一些不常用属性作为替代。若因此产生冲突,请禁用本插件。
 * @author Frodez
 * @date 2019-12-08
 */
@Slf4j
@Component
@Profile({ "dev", "test" })
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 300)
public class DefaultControllerPlugin implements ApiListingBuilderPlugin {

	private boolean useCustomerizedPluggins = false;

	@Autowired
	public DefaultControllerPlugin(SwaggerProperties properties) {
		this.useCustomerizedPluggins = properties.getUseCustomerizedPluggins();
	}

	@Override
	public boolean supports(DocumentationType delimiter) {
		return useCustomerizedPluggins;
	}

	@Override
	public void apply(ApiListingContext context) {
		Optional<? extends Class<?>> controller = context.getResourceGroup().getControllerClass();
		if (controller.isPresent()) {
			resolveController(context, controller.get());
		}
	}

	private void resolveController(ApiListingContext context, Class<?> controller) {
		if (findAnnotation(controller, Api.class) != null) {
			return;
		}
		ControllerInfo info = resolveNameAttribute(controller);
		if (info == null) {
			log.warn(StrUtil.concat(controller.getName(), "的信息未配置"));
		}
		ApiListingBuilder builder = context.apiListingBuilder();
		ReflectUtil.set(ApiListingBuilder.class, "tagNames", builder, Set.of(info.name));
		builder.consumes(Set.of(info.consumes));
		builder.produces(Set.of(info.produces));
	}

	private ControllerInfo resolveNameAttribute(Class<?> controller) {
		ControllerInfo info = new ControllerInfo();
		boolean isRestController = isRestController(controller);
		GetMapping getMapping = findAnnotation(controller, GetMapping.class);
		if (getMapping != null) {
			info.name = getMapping.name();
			info.consumes = getMapping.consumes();
			info.produces = resolveJsonInfo(isRestController, getMapping.produces());
			return info;
		}
		PostMapping postMapping = findAnnotation(controller, PostMapping.class);
		if (postMapping != null) {
			info.name = postMapping.name();
			info.consumes = postMapping.consumes();
			info.produces = resolveJsonInfo(isRestController, postMapping.produces());
			return info;
		}
		DeleteMapping deleteMapping = findAnnotation(controller, DeleteMapping.class);
		if (deleteMapping != null) {
			info.name = deleteMapping.name();
			info.consumes = deleteMapping.consumes();
			info.produces = resolveJsonInfo(isRestController, deleteMapping.produces());
			return info;
		}
		PutMapping putMapping = findAnnotation(controller, PutMapping.class);
		if (putMapping != null) {
			info.name = putMapping.name();
			info.consumes = putMapping.consumes();
			info.produces = resolveJsonInfo(isRestController, putMapping.produces());
			return info;
		}
		RequestMapping requestMapping = findAnnotation(controller, RequestMapping.class);
		if (requestMapping != null) {
			info.name = requestMapping.name();
			info.consumes = requestMapping.consumes();
			info.produces = resolveJsonInfo(isRestController, requestMapping.produces());
			return info;
		}
		return null;
	}

	private boolean isRestController(Class<?> controller) {
		return findAnnotation(controller, RestController.class) != null || findAnnotation(controller, ResponseBody.class) != null;
	}

	private String[] resolveJsonInfo(boolean isRestController, String[] strings) {
		if (!isRestController) {
			return strings;
		}
		boolean isAbsent = true;
		for (String string : strings) {
			if (string.equals(MediaType.APPLICATION_JSON_VALUE)) {
				isAbsent = false;
				break;
			}
		}
		if (isAbsent) {
			if (strings.length == 0) {
				return new String[] { MediaType.APPLICATION_JSON_VALUE };
			}
			String[] newStrings = new String[strings.length + 1];
			System.arraycopy(strings, 0, newStrings, 0, strings.length);
			newStrings[strings.length] = MediaType.APPLICATION_JSON_VALUE;
			return newStrings;
		} else {
			return strings;
		}
	}

	@Getter
	private static class ControllerInfo {

		public String name;

		public String[] consumes;

		public String[] produces;

	}

}