package de.yannicpunktdee.yoshibot.utils;

import lombok.SneakyThrows;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.TextChannel;
import org.apache.commons.io.FileUtils;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

public class StatusProvider implements Provider {
	
	private static final ScheduledExecutorService statusScheduler = Executors.newScheduledThreadPool(1);
	
	private int lastPlayersOnline = -1;
	
	private final String message_id;
	
	private final int serverPort;
	
	private final String desc;
	
	private LocalDateTime timestampLastPlayerOnline;
	
	private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss E, dd.MM.yyyy",
																						Locale.GERMANY);
	
	private final ProcessBuilder mcstatus = new ProcessBuilder();
	
	
	@SneakyThrows
	public StatusProvider(String desc, String messageId, Guild guild, int secondsPerTime, int serverPort) {
		this.desc       = desc;
		this.message_id = messageId;
		this.serverPort = serverPort;
		this.mcstatus.command(Resources.getPath_to_mcstatus(), Resources.getMc_server() + ":" + serverPort, "json");
		
		File timeStampSave = new File(String.format("%s/%d.txt", Resources.getResourcePath(), serverPort));
		timestampLastPlayerOnline = null;
		
		if (!timeStampSave.createNewFile()) {
			List<String> contents = Files.readAllLines(timeStampSave.toPath());
			if (contents.size() > 0) {
				timestampLastPlayerOnline = LocalDateTime.parse(contents.get(0), TIME_FORMATTER);
			}
		}
		
		TextChannel statusChannel = guild.getTextChannelById(Resources.getStatus_channel());
		statusScheduler.scheduleAtFixedRate(() -> updateStatusMessage(statusChannel), 0, secondsPerTime,
											TimeUnit.SECONDS);
	}
	
	@SuppressWarnings("unchecked")
	public void updateStatusMessage(TextChannel statusChannel) {
		EmbedBuilder eb = new EmbedBuilder();
		eb.setTitle(desc);
		eb.addField("IP", Resources.getMc_server() + ":" + serverPort, false);
		
		try {
			Map<String, Object> serverInfo = getPlayersOnline();
			if ((boolean) serverInfo.get("online")) {
				if ((boolean) serverInfo.get("Server starting")) {
					eb.addField("Server still Starting", "True", false);
				} else {
					int newPlayersOnline = (int) serverInfo.get("playerCount");
					if (newPlayersOnline == lastPlayersOnline) return;
					else {
						if (timestampLastPlayerOnline == null && newPlayersOnline == 0 && lastPlayersOnline != -1) {
							timestampLastPlayerOnline = LocalDateTime.now();
						} else if (timestampLastPlayerOnline != null && newPlayersOnline > 0) {
							timestampLastPlayerOnline = null;
						}
						lastPlayersOnline = newPlayersOnline;
					}
					eb.addField("Version", (String) serverInfo.get("version"), true);
					eb.addField("MOTD", (String) serverInfo.get("motd"), true);
					eb.addField("Spieler online", lastPlayersOnline + " / " + serverInfo.get("playerMax"), false);
					if (timestampLastPlayerOnline != null) {
						eb.addField("Zuletzt gesehen", TIME_FORMATTER.format(timestampLastPlayerOnline), false);
					}
					if (lastPlayersOnline > 0) {
						eb.addField("Spieler:", String.join(", ", (List<String>) serverInfo.get("playerNames")), false);
					}
				}
			} else {
				eb.addField("Offline", "", false);
			}
			
			statusChannel.editMessageById(this.message_id, eb.build()).queue();
		} catch (IOException e) {
			Logger.logError(e.toString());
		}
	}
	
	private Map<String, Object> getPlayersOnline() throws IOException {
		
		Process process = mcstatus.start();
		
		try {
			process.waitFor();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		if (process.exitValue() != 0) {
			Logger.logError("MCStatus on port " + serverPort + " exited with errorcode " + process.exitValue());
		}
		
		String output = new BufferedReader(new InputStreamReader(process.getInputStream())).lines().collect(
				Collectors.joining());
		
		Map<String, Object> result = new HashMap<>();
		
		if (output.startsWith("The server did not respond to the query protocol.")) {
			result.put("online", false);
			return result;
		}
		JSONObject obj = new JSONObject(output);
		
		result.put("online", obj.getBoolean("online"));
		
		if (!obj.getBoolean("online")) {
			return result;
		}
		for (String key : new String[]{"player_count", "player_max", "players", "motd", "version"}) {
			if (!obj.has(key)) {
				result.put("Server starting", true);
				return result;
			}
		}
		result.put("Server starting", false);
		
		result.put("playerCount", obj.getInt("player_count"));
		result.put("playerMax", obj.getInt("player_max"));
		result.put("playerNames",
				   StreamSupport.stream(obj.getJSONArray("players").spliterator(), false)
						   .map(jsonobj -> ((JSONObject) jsonobj).getString("name")).sorted().collect(
								   Collectors.toList()));
		result.put("motd", obj.getString("motd"));
		result.put("version", obj.getString("version"));
		
		return result;
	}
	
	@SneakyThrows
	@Override
	public void onStop() {
		Logger.logInfo(String.format("Stopping StatusProvider for \"%s\" on Port %d", desc, serverPort));
		if (timestampLastPlayerOnline != null) {
			FileUtils.writeStringToFile(new File(Resources.getResourcePath() + "/" + serverPort + ".txt"),
										TIME_FORMATTER.format(timestampLastPlayerOnline), "UTF" +
																						  "-8");
		}
	}
}
