package dev.peerat.backend.bonus.extract;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import dev.peerat.backend.model.PeerAtUser;
import dev.peerat.framework.RequestType;
import dev.peerat.framework.Response;
import dev.peerat.framework.Route;
import dev.peerat.framework.RouteMapper;
import dev.peerat.framework.Router;
import dev.peerat.framework.utils.json.JsonArray;
import dev.peerat.framework.utils.json.JsonMap;

public class RouteExtracter {
	
	private Router<PeerAtUser> router;
	
	public RouteExtracter(Router<PeerAtUser> router){
		this.router = router;
	}

	public void extract() throws Exception{
		RouteMapper[] mappers = getField(Router.class, router, "mappers");
		for(RequestType type : RequestType.values()){
			RouteMapper mapper = mappers[type.ordinal()];
			Response[] responses = getField(RouteMapper.class, mapper, "responses");
			Route[] routes = getField(RouteMapper.class, mapper, "routes");
			synchronized (responses){
				for(int i = 0; i < responses.length; i++){
					Route route = routes[i];
					System.out.println("["+type+"] ("+route.needLogin()+") "+route.path());
				}
			}
		}
	}
	
	public void extractDoc() throws Exception{
		RouteMapper[] mappers = getField(Router.class, router, "mappers");
		for(RequestType type : RequestType.values()){
			RouteMapper mapper = mappers[type.ordinal()];
			Response[] responses = getField(RouteMapper.class, mapper, "responses");
			synchronized (responses){
				for(int i = 0; i < responses.length; i++){
					Response response = responses[i];
					Method method = response.getClass().getDeclaredMethod("exec",
							Response.class.getDeclaredMethods()[0].getParameterTypes());
					for(RouteDoc doc : method.getDeclaredAnnotationsByType(RouteDoc.class)){
						System.out.println(doc.path()+((doc.path().isEmpty() ? "":"\n"))+" ["+doc.responseCode()+"] "+doc.responseDescription());
					}
				}
			}
		}
	}
	
	public JsonMap swagger(String host) throws Exception{
		JsonMap result = new JsonMap();
		result.set("swagger","2.0");
		JsonMap info = new JsonMap();
		info.set("title", "Peer-at-code backend api routes");
		info.set("description", "Using Peer-at Code Framework");
		result.set("info", info);
		result.set("host", host);
		result.set("basePath","/");
		
		List<Response> routes = new ArrayList<>();
		RouteMapper[] mappers = getField(Router.class, router, "mappers");
		for(RequestType type : RequestType.values()){
			RouteMapper mapper = mappers[type.ordinal()];
			Response[] responses = getField(RouteMapper.class, mapper, "responses");
			synchronized (responses){
				routes.addAll(Arrays.asList(responses));
			}
		}
		
		Set<String> packages = new HashSet<>();
		for(Response response : routes){
			String name = response.getClass().getPackage().getName();
			name = name.substring(name.lastIndexOf('.')+1, name.length());
			packages.add(name);
		}
		JsonArray tags = new JsonArray();
		for(String tag : packages){
			JsonMap current = new JsonMap();
			current.set("name", tag);
			tags.add(current);
		}
		result.set("tags", tags);
		JsonArray schemes = new JsonArray();
		schemes.add("https");
		result.set("schemes", schemes);
		
		JsonMap paths = new JsonMap();
		
		for(Response response : routes){
			Method method = response.getClass().getDeclaredMethod("exec",
					Response.class.getDeclaredMethods()[0].getParameterTypes());
			Route route = method.getDeclaredAnnotation(Route.class);
			RouteDoc[] docs = method.getDeclaredAnnotationsByType(RouteDoc.class);
			if(docs.length < 1) continue;
			RouteDoc base = docs[0];
			JsonMap current = new JsonMap();
			JsonMap data = new JsonMap();
			JsonArray tag = new JsonArray();
			String pack = response.getClass().getPackage().getName();
			pack = pack.substring(pack.lastIndexOf('.')+1, pack.length());
			tag.add(pack);
			data.set("tags", tag);
			
			
			current.set(route.type().toString().toLowerCase(), data);
			
			paths.set(base.path(), current);
		}
		
		result.set("paths", paths);
		
		return result;
	}
	
	private <E> E getField(Class<?> clazz, Object instance, String name) throws Exception{
		Field field = clazz.getDeclaredField(name);
		field.setAccessible(true);
		return (E) field.get(instance);
	}
}
