package de.yannicpunktdee.yoshibot.utils;

import de.yannicpunktdee.yoshibot.main.YoshiBot;
import lombok.Getter;
import lombok.SneakyThrows;
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Function;
import java.util.stream.StreamSupport;

public final class Resources {
	
	@Getter
	private static boolean greetings_and_byebyes_on;
	
	@Getter
	private static int statusUpdate;
	
	@Getter
	private static long guild_id;
	
	@Getter
	private static String resourcePath, configPath, audioPath, tempPath, activitiesPath, greetingsPath, byebyesPath,
			sauceConfigPath, ttsPath, patPngPath, imagePath, bonkPngPath, jda_builder_string, status_channel,
			path_to_mcstatus, comebacksPath, departsPath, mcserver_config_file;
	
	private static List<String> greetings, byebyes, departs, comebacks;
	
	@Getter
	private static String[] restrict_commands_to_channel, filteredTags;
	
	@Getter
	private static final Map<String, List<String>> feedDetails = new HashMap<>();
	
	
	private static Properties propertiesFile;
	
	
	public static boolean init(String resourcePathArg) {
		boolean isOk = initResources(resourcePathArg);
		if (isOk) isOk = initConfig();
		if (isOk) isOk = initAudio();
		if (isOk) isOk = initTemp();
		if (isOk) isOk = initActivities();
		if (isOk) isOk = initGreetingsAndByebyes();
		if (isOk) isOk = initSauceConfig();
		if (isOk) isOk = initTTS();
		if (isOk) isOk = initJdaBuilderString();
		if (isOk) isOk = initGuildId();
		if (isOk) isOk = initChannelRestrict();
		if (isOk) isOk = initTagFilter();
		if (isOk) isOk = initPatPngPath();
		if (isOk) isOk = initImages();
		if (isOk) isOk = initBonkPngPath();
		if (isOk) isOk = initStatusMessage();
		
		if (isOk) Logger.logInfo("Die Konfigurationen wurden erfolgreich geladen.");
		else Logger.logError("Die Konfiguration konnte nicht geladen werden");
		
		return isOk;
	}
	
	private static boolean initResources(String resourcePathArg) {
		Logger.logInfo("Versuche Resource-Verzeichnis zu finden.");
		
		resourcePath = (new File((resourcePathArg == null) ? "rsc" : resourcePathArg)).getAbsolutePath()
							   .replace('\\', '/') + "/";
		
		return verifyExists(resourcePath, File::isDirectory) != null;
	}
	
	@SneakyThrows
	private static boolean initConfig() {
		configPath = verifyExists(resourcePath + "Config.properties", File::isFile);
		
		if (configPath == null) {
			return false;
		}
		
		propertiesFile = new Properties();
		propertiesFile.load(new FileInputStream(configPath));
		
		return true;
	}
	
	private static boolean initAudio() {
		return (audioPath = verifyExists(resourcePath + "audio/", File::isDirectory)) != null;
	}
	
	public static String getPathToAudioFile(String name) {
		String filePathWithoutExtension = audioPath + name;
		if (new File(filePathWithoutExtension + ".opus").isFile()) {
			return filePathWithoutExtension + ".opus";
		} else if (new File(filePathWithoutExtension + ".mp3").isFile()) {
			return filePathWithoutExtension + ".mp3";
		} else {
			return "";
		}
	}
	
	private static boolean initTemp() {
		Logger.logInfo("Versuche Temp-Verzeichnis zu finden.");
		
		String theoreticalTempPath = System.getProperty("java.io.tmpdir").replace('\\', '/') + "/yoshibot/";
		
		tempPath = verifyExists(theoreticalTempPath, File::isDirectory);
		if (tempPath != null) {
			return true;
		}
		File tempDir = new File(theoreticalTempPath);
		
		if (tempDir.mkdir()) {
			return verifyExists(tempDir.getAbsolutePath(), File::isDirectory) != null;
		} else {
			Logger.logError("Temp-Verzeichnis konnte nicht erstellt werden.");
			return false;
		}
	}
	
	public static String getEnsuredTempPath() {
		if (tempPath == null) {
			initTemp();
		}
		File tempDir = new File(tempPath);
		if (!tempDir.isDirectory())
			if (!tempDir.mkdir()) throw new Error("Could not make Temp directory");
		return tempPath;
	}
	
	private static boolean initActivities() {
		activitiesPath = verifyExists(resourcePath + "activities.txt", File::isFile);
		return activitiesPath != null;
	}
	
	private static boolean initSauceConfig() {
		sauceConfigPath = verifyExists(resourcePath + "sauceConfig.json", File::isFile);
		return sauceConfigPath != null;
	}
	
	private static boolean initTTS() {
		ttsPath = verifyExists(resourcePath + "tts.py", File::isFile);
		return ttsPath != null;
	}
	
	@SneakyThrows
	private static boolean initJdaBuilderString() {
		if (verifyExists(resourcePath + "PrivateJdaBuilderString.txt", File::isFile) == null) {
			return false;
		}
		jda_builder_string = Files.readAllLines(new File(resourcePath + "PrivateJdaBuilderString.txt").toPath()).get(0);
		Logger.logInfo("jda_builder_string erfolgreich geladen");
		return true;
	}
	
	private static boolean initPatPngPath() {
		patPngPath = verifyExists(resourcePath + "pats/", File::isDirectory);
		return patPngPath != null;
	}
	
	private static boolean initBonkPngPath() {
		bonkPngPath = verifyExists(resourcePath + "bonks/", File::isDirectory);
		return bonkPngPath != null;
	}
	
	private static boolean initGuildId() {
		if (!propertiesFile.containsKey("guild_id")) {
			Logger.logError("Die Config.properties benötigt das Attribut guild_id.");
			return false;
		}
		String raw = propertiesFile.getProperty("guild_id");
		try {
			guild_id = Long.parseLong(raw);
		} catch (NumberFormatException e) {
			Logger.logError("Die angegebene guild_id ist keine Ganzzahl");
			return false;
		}
		Logger.logInfo("guild_id erfolgreich geladen");
		return true;
	}
	
	@SneakyThrows
	private static boolean initGreetingsAndByebyes() {
		greetingsPath = verifyExists(resourcePath + "greetings.txt", File::isFile);
		if (greetingsPath == null) {
			return false;
		}
		byebyesPath   = verifyExists(resourcePath + "byebyes.txt", File::isFile);
		comebacksPath = verifyExists(resourcePath + "comebacks.txt", File::isFile);
		departsPath   = verifyExists(resourcePath + "departs.txt", File::isFile);
		
		if (propertiesFile.containsKey("greetings_and_byebyes_on")) {
			greetings_and_byebyes_on = Boolean.parseBoolean(propertiesFile.getProperty("greetings_and_byebyes_on"));
			if (!greetings_and_byebyes_on) return true;
		} else greetings_and_byebyes_on = true;
		
		greetings = Files.readAllLines(Paths.get(greetingsPath));
		byebyes   = Files.readAllLines(Paths.get(byebyesPath));
		comebacks = Files.readAllLines(Paths.get(comebacksPath));
		departs   = Files.readAllLines(Paths.get(departsPath));
		return true;
	}
	
	public static String getRandomGreeting(String name) {
		return greetings_and_byebyes_on ? String.format(getRandomFrom(greetings), name) : null;
	}
	
	public static String getRandomByebye(String name) {
		return greetings_and_byebyes_on ? String.format(getRandomFrom(byebyes), name) : null;
	}
	
	public static String getRandomAfk(String name, boolean didComeBack) {
		return greetings_and_byebyes_on ? String.format(getRandomFrom(didComeBack ? comebacks : departs), name) : null;
	}
	
	private static String getRandomFrom(List<String> pool) {
		return pool.get(YoshiBot.getInstance().getRandom().nextInt(pool.size()));
	}
	
	private static boolean initChannelRestrict() {
		if (propertiesFile.containsKey("restrict_commands_to_channel"))
			restrict_commands_to_channel = propertiesFile.getProperty("restrict_commands_to_channel").split("\\s+");
		return true;
	}
	
	private static boolean initTagFilter() {
		try {
			JSONObject configBase = new JSONObject(
					String.join("\n",
								Files.readAllLines(new File(sauceConfigPath).toPath())));
			JSONArray filter = configBase.getJSONArray("tags_general_filter");
			filteredTags =
					StreamSupport.stream(filter.spliterator(), false).map(i -> (String) i).toArray(String[]::new);
			JSONArray feeds = configBase.getJSONArray("feeds");
			for (Object feedConfigObj : feeds) {
				JSONObject feedConfig = (JSONObject) feedConfigObj;
				List<String> tags = new ArrayList<>();
				for (Object tagObj : feedConfig.getJSONArray("tags")) {
					if (tagObj instanceof String) {
						tags.add((String) tagObj);
					} else if (tagObj instanceof JSONArray) {
						StringBuilder sb = new StringBuilder().append("(%20");
						sb.append(((JSONArray) tagObj).join("%20~%20"));
						tags.add(sb.append("%20)").toString().replace("\"", ""));
					}
				}
				feedDetails.put(feedConfig.getString("channel"), tags);
			}
		} catch (IOException e) {
			return false;
		}
		
		Logger.logInfo("tags_general_filter erfolgreich geladen");
		return true;
	}
	
	private static boolean initImages() {
		imagePath = verifyExists(resourcePath + "image/", File::isDirectory);
		if (imagePath != null) {
			return true;
		}
		if (new File(resourcePath + "image/").mkdir()) {
			imagePath = verifyExists(resourcePath + "image/", File::isDirectory);
			Logger.logInfo("Bildordner erzeugt");
			return true;
		} else {
			Logger.logError("Konnte Bildordner nicht erzeugen!");
			return false;
		}
	}
	
	private static String verifyExists(String filename, Function<File, Boolean> checkIsValidFile) {
		String[] split = filename.split("/");
		Logger.logDebug(String.format("Versuche %s zu finden.", split[split.length - 1]));
		if (checkIsValidFile.apply(new File(filename))) {
			return filename;
		} else {
			Logger.logError(String.format("%s konnte nicht gefunden werden", filename));
			return null;
		}
	}
	
	private static boolean initStatusMessage() {
		if (propertiesFile.containsKey("path_to_mcstatus") &&
			propertiesFile.containsKey("status_channel") &&
			propertiesFile.containsKey("path_to_status_json")) {
			status_channel       = propertiesFile.getProperty("status_channel");
			path_to_mcstatus     = propertiesFile.getProperty("path_to_mcstatus");
			mcserver_config_file = propertiesFile.getProperty("path_to_status_json");
			try {
				statusUpdate = Integer.parseInt(propertiesFile.getProperty("status_update"));
			} catch (NumberFormatException e) {
				return false;
			}
			return true;
		} else return false;
	}
	
	public static String getProperty(String key) {
		return propertiesFile.getProperty(key);
	}
	
	@SneakyThrows
	public synchronized static void setProperty(String key, String value) {
		propertiesFile.setProperty(key, value);
		
		propertiesFile.store(new FileOutputStream(configPath), "");
	}
	
}
