package dev.peerat.backend.routes;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;

import dev.peerat.backend.Configuration;
import dev.peerat.backend.Main;
import dev.peerat.backend.model.PeerAtUser;
import dev.peerat.backend.repository.DatabaseRepository;
import dev.peerat.framework.Locker;
import dev.peerat.framework.Locker.Key;
import dev.peerat.framework.RequestType;
import dev.peerat.framework.Router;

public class StateTestCase{
	
	@Test
	void main() throws Exception{
		Locker<Boolean> locker = new Locker<>();
		Thread server = new Thread(new Runnable(){
			@Override
			public void run(){
				try {
					locker.setValue(true);
					Main.main(null);
				} catch (Exception e){
					e.printStackTrace();
				};
			}
		});
		
		Key key = new Key();
		locker.init(key);
		server.start();
		Thread.sleep(3000);
		locker.lock(key);
		locker.unlock(key);
		
		Configuration config = new Configuration("config.txt");
		config.load();
		
		DatabaseRepository repo = new DatabaseRepository(config);
		
		Field field = Main.class.getDeclaredField("ACCESS_ROUTER");
		field.setAccessible(true);
		Router<PeerAtUser> router = (Router<PeerAtUser>) field.get(null);
		Function<String, String> tokenProvider = (email) -> {
			try{
				return router.createAuthUser(new PeerAtUser(repo.getPlayerId(email)));
			}catch(Exception ex){
				ex.printStackTrace();
				return null;
			}
		};
		
		File dir = new File("./route-test/");
		for(File cases : dir.listFiles()){
			if(!cases.isDirectory()) continue;
			List<File> responses = new ArrayList<>();
			File dbsetup = null;
			for(File file : cases.listFiles()){
				if(file.getName().endsWith(".class")) dbsetup = file; else responses.add(file);
			}
//			Class<?> clazz = new URLClassLoader(new URL[]{cases.toURI().toURL()}).loadClass(dbsetup.getAbsolutePath().substring(cases.getAbsolutePath().length()+1).replace("/", ".").replace("\\", ".").replace(".class", ""));
//			DatabaseSeeder seeder = (DatabaseSeeder) clazz.newInstance();
//			seeder.setup();
			for(File file : responses){
				StateCase state = new StateCase(file, tokenProvider);
				state.execute("127.0.0.1", config.getTcpPort());
			}
		}
	}
	
	public static class StateCase{
		
		private static final Pattern TOKEN_PATTERN = Pattern.compile("^(.*)<token:(.*)>(.*)$");
		
		private String path;
		private RequestType type;
		private List<String> headers;
		private String payload;
		
		private int responseCode;
		private List<String> responseHeaders;
		private String responsePayload;
		
		public StateCase(File file, Function<String, String> tokenProvider) throws Exception{
			BufferedReader reader = new BufferedReader(new FileReader(file));
			String line;
			String payloadBuffer;
			this.headers = new ArrayList<>();
			this.responseHeaders = new ArrayList<>();
			
			this.path = reader.readLine();
			this.type = RequestType.valueOf(reader.readLine());
			
			while(((line = reader.readLine() )!= null) && (!line.isEmpty())) headers.add(line);
			
			payloadBuffer = "";
			while(((line = reader.readLine() )!= null) && (!line.isEmpty())) payloadBuffer+=line;
			this.payload = payloadBuffer;
			
			this.responseCode = Integer.parseInt(reader.readLine());
			while(((line = reader.readLine() )!= null) && (!line.isEmpty())) responseHeaders.add(line);
			
			payloadBuffer = "";
			while(((line = reader.readLine() )!= null) && (!line.isEmpty())) payloadBuffer+=line;
			this.responsePayload = payloadBuffer;
			
			reader.close();
			
			this.headers = Arrays.asList(this.headers.stream().<String>map((header) -> {
				Matcher matcher = TOKEN_PATTERN.matcher(header);
				return matcher.matches() ? matcher.group(1)+tokenProvider.apply(matcher.group(2))+matcher.group(3) : header;
			}).toArray((size) -> new String[size]));
		}
		
		public void execute(String host, int port) throws Exception{
			Socket socket = new Socket(host, port);
			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			writer.write(this.type.toString()+" "+path+" HTTP/1.1\n");
			for(String header : headers) writer.write(header+"\n");
			writer.write("\n");
			if(payload != null) writer.write(payload+"\n");
			writer.flush();
			
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			int responseCode;
			Set<String> responseHeaders = new HashSet<>();
			String responseBody = "";
			
			String line;
			responseCode = Integer.parseInt(reader.readLine().split("\\s+")[1]);
			while(((line = reader.readLine() )!= null) && (!line.isEmpty())) responseHeaders.add(line);
			
			while(((line = reader.readLine() )!= null) && (!line.isEmpty())) responseBody+=line;

			socket.close();
			
			if(this.responseCode != responseCode) throw new AssertionFailedError("Excepted "+this.responseCode+" but have "+responseCode);
			
		}
	}
		
}