package dev.peerat.backend;

import static dev.peerat.framework.RequestType.OPTIONS;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Predicate;
import java.util.regex.Matcher;

import dev.peerat.backend.bonus.extract.RouteExtracter;
import dev.peerat.backend.model.Group;
import dev.peerat.backend.model.PeerAtUser;
import dev.peerat.backend.repository.ConnectionManager;
import dev.peerat.backend.repository.DatabaseRepository;
import dev.peerat.framework.Context;
import dev.peerat.framework.DependencyInjector;
import dev.peerat.framework.HttpReader;
import dev.peerat.framework.HttpWriter;
import dev.peerat.framework.Locker;
import dev.peerat.framework.Locker.Key;
import dev.peerat.framework.RequestType;
import dev.peerat.framework.Response;
import dev.peerat.framework.Route;
import dev.peerat.framework.RouteInterceptor;
import dev.peerat.framework.Router;
import dev.peerat.framework.auth.Authenticator;
import dev.peerat.framework.auth.JwtAuthenticator;
import dev.peerat.framework.utils.json.JsonMap;
import dev.peerat.framework.utils.json.JsonParser;

public class Main{
	
	private static Router<PeerAtUser> ACCESS_ROUTER;
	
	public static void main(String[] args) throws Exception{
		Configuration config = new Configuration("config.txt").addDefaultValue("users_files", "/tmp/users_files");
		config.load();

		Class.forName("com.mysql.cj.jdbc.Driver");
		
		DatabaseRepository repo = new DatabaseRepository(new ConnectionManager(config), config);
		Router<PeerAtUser> router = new Router<PeerAtUser>()
				.activeReOrdering().
				addDefaultHeaders(RequestType.GET, "Access-Control-Allow-Origin: *").
				addDefaultHeaders(RequestType.POST, "Access-Control-Allow-Origin: *").
				addDefaultHeaders(RequestType.OPTIONS,
						"Access-Control-Allow-Origin: *",
						"Access-Control-Allow-Methods: *",
						"Access-Control-Allow-Headers: *");
		ACCESS_ROUTER = router;
		
		Authenticator<PeerAtUser> auth;
		if(config.getJwtKey() != null){
			JsonParser parser = new JsonParser();
			JsonMap json = parser.parse(config.getJwtKey());
			Map<String, Object> params = new HashMap<>();
			for(Entry<String, Object> entry : json.entries()) params.put(entry.getKey(), entry.getValue());
			auth = new JwtAuthenticator<PeerAtUser>().configure(
					(builder) -> builder.setExpectedIssuer(config.getTokenIssuer()),
					(claims) -> {
						claims.setIssuer(config.getTokenIssuer());
						claims.setExpirationTimeMinutesInTheFuture(config.getTokenExpiration());
					}, 
					(claims) -> new PeerAtUser(claims),
					params);
		}else{
			auth = new JwtAuthenticator<PeerAtUser>().configure(
					(builder) -> builder.setExpectedIssuer(config.getTokenIssuer()),
					(claims) -> {
						claims.setIssuer(config.getTokenIssuer());
						claims.setExpirationTimeMinutesInTheFuture(config.getTokenExpiration());
					}, 
					(claims) -> new PeerAtUser(claims));
			JsonMap json = new JsonMap();
			for(Entry<String, Object> entry : ((JwtAuthenticator<PeerAtUser>)auth).exportKey().entrySet()){
				json.set(entry.getKey(), entry.getValue());
			}
			config.setJwtKey(json.toString());
			config.save();
		}
		router.setAuthenticator(auth);

		router.setDefault((matcher, context, reader, writer) -> {
			context.response(404);
			writer.write("404 not Found.\n");
		});
		
		router.register(new Response(){
			@Route(path = "^(.*)$", type = OPTIONS)
			public void exec(Matcher matcher, Context context, HttpReader reader, HttpWriter writer) throws Exception {
				context.response(200);
			}
		});
		
		initRoutes(router, repo, config);
		
		router.addInterceptor(new RouteInterceptor(){
			@Override
			public boolean intercept(Matcher matcher, Context context, HttpReader reader, HttpWriter writer, Method method){
				if(method.getDeclaringClass().getPackage().getName().contains(".admins")){
					try {
						Group group = repo.getGroupRepository().getPlayerGroup(context.<PeerAtUser>getUser().getId(), 1);
						if(!group.getName().equalsIgnoreCase("Quarter-Master - Battles PAC x CEI")){
							context.response(401);
							return false;
						}
					}catch(Exception ex){
						ex.printStackTrace();
						try{
							context.response(401);
						}catch(Exception e){}
						return false;
					}
				}
				return true;
			}
		});
		
		new Thread(new Runnable(){
			public void run(){
				Key key = new Key();
				
				Locker<Context> locker = router.getLogger();
				
				locker.init(key);
				try {
					while(true){
						locker.lock(key);
						Context instance = locker.getValue(key);
						if(instance == null) continue;
						System.out.println("["+((instance.isLogged()) ? repo.getPlayerRepository().getPlayer(instance.<PeerAtUser>getUser().getId()).getPseudo() : "?")+"] "+instance.getType()+" "+instance.getPath()+" -> "+instance.getResponseCode());
					}
				}catch(Exception e){
					e.printStackTrace();
				}
				locker.remove(key);
			}
		}).start();
		
		new Thread(new Runnable(){
			public void run(){
				Key key = new Key();
				
				Locker<Throwable> locker = router.getExceptionLogger();
				
				locker.init(key);
				try {
					while(true){
						locker.lock(key);
						locker.getValue(key).printStackTrace();
					}
				}catch(Exception e){
					e.printStackTrace();
				}
				locker.remove(key);
			}
		}).start();
		
		if(config.useSsl()) router.configureSSL(config.getSslKeystore(), config.getSslKeystorePasswd());
		router.listen(config.getTcpPort(), config.useSsl());
	}

	private static void initRoutes(Router<PeerAtUser> router, DatabaseRepository repo, Configuration config) throws Exception{
		
		Predicate<PeerAtUser> isAdmin = (user) -> {
			try {
				Group group = repo.getGroupRepository().getPlayerGroup(user.getId(), 1);
				return group.getName().equalsIgnoreCase("Quarter-Master - Battles PAC x CEI");
			}catch(Exception ex){}
			return false;
		};
		
		router.registerPackages("dev.peerat.backend.routes",new DependencyInjector()
				.of(repo, router, config, new RouteExtracter(router), config.getMail(), isAdmin)
				.of("waitting", new HashMap<>())
				.of("leaderboard", new Locker<>())
				.of("groups", new Locker<>())
				.of("groupMessages", new Locker<>()));
		
//		Bot bot = new Bot(config, repo, groupLock);
//		bot.start();
	}
}
