/* Copyright 2023 m-privacy GmbH.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */


#ifndef WIN32

#include <iostream>
#include <nlohmann/json.hpp>
#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <curl/curl.h>
#include <libgen.h>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <archive.h>
#include <archive_entry.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <magic.h>
#include <stdbool.h>
#include <signal.h>
#include <ftw.h>

#if !defined(WIN32) && !defined(WIN64)
#include <syslog.h>
#endif

#include <rfb/LogWriter.h>

#include <tgpro_environment.h>
#include <mp_utils.h>

#include "SAutotransferVaithex.h"
#include "SAutotransferMime.h"
#include <magic.h>
#define MAGIC_ALLOWED 0
#define MAGIC_NOT_ALLOWED 1
#define MAGIC_FAILED -1

using namespace std;
using json = nlohmann::json;

static rfb::LogWriter vlog("SAutotransferVaithex");

rfb::StringParameter vaithexIp("vaithexIp","IP address of the VAITHEX server to clean files and (auto)-download them to the client","");

rfb::StringParameter vaithexPort("vaithexPort","Port of the VAITHEX server to clean files and (auto)-download them to the client","443");

rfb::IntParameter vaithexTimeout("vaithexTimeout", "VAITHEX timeout in seconds (min 60, max 3600)", 300, 60, 3600);

rfb::StringParameter vaithexScanners
("vaithexScanners",
 "List of scanners the VAITHEX server should use to process files",
 "");  // Empty default. It will be populated dynamically

char* vaithexScannersAsJsonStr = NULL;

std::vector<std::string> getAvailableScannersFromVaithex(const char* caCertPath);

// Declare job_id at a higher scope, accessible to both createJob and uploadPayload functions
long job_id = 0;
std::string token;
char uploadUrl[600];
char jobsPostBody[1024];
char metadata_json[4096];

// Function declarations
void removeNonPrintableCharacters(char* str, size_t size);
static int extractAll(const char* filename, const char* outputDir, char* lastExtractedFile);

// Callback function for nftw() to remove files and directories
static int removeCallback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
	int rv = remove(fpath);
	if (rv) {
		vlog.error("Failed to remove '%s': %s", fpath, strerror(errno));
	} else {
		vlog.debug("Removed: %s", fpath);
	}
	return rv;
}

// Recursively remove a directory and all its contents
static int removeDirectory(const char* path) {
	// FTW_DEPTH: do depth-first search (process directory contents before the directory itself)
	return nftw(path, removeCallback, 64, FTW_DEPTH | FTW_PHYS);
}

void cleanupTempDir(const char* dirPath) {
	if (!dirPath || strlen(dirPath) == 0) {
		vlog.error("%s: dirPath is NULL or empty", __func__);
		return;
	}
	// Only delete allowed directories
	if (strncmp(dirPath, "/home/tmpdir/tmp", 16) != 0 &&
		strncmp(dirPath, "/tmp/vaithex", 12) != 0) {
		vlog.error("%s: Refusing to delete directory outside allowed paths: %s", __func__, dirPath);
	return;
		}
		// Check if directory exists
		struct stat st;
		if (stat(dirPath, &st) != 0) {
			vlog.debug("%s: Directory does not exist (already cleaned?): %s", __func__, dirPath);
			return;
		}

		if (!S_ISDIR(st.st_mode)) {
			vlog.error("%s: Path exists but is not a directory: %s", __func__, dirPath);
			return;
		}

		vlog.debug("%s: Cleaning up temporary directory: %s", __func__, dirPath);

		int result = removeDirectory(dirPath);

		if (result != 0) {
			vlog.error("%s: Failed to remove temporary directory: %s", __func__, dirPath);
		} else {
			vlog.debug("%s: Successfully cleaned up: %s", __func__, dirPath);
		}
}

void cleanupTempTarFiles(const char* filename) {
	if (!filename || strlen(filename) == 0) {
		vlog.error("%s: filename is NULL or empty", __func__);
		return;
	}

	vlog.debug("%s: Cleaning up temporary tar files for: %s", __func__, filename);

	char tarFile[512];
	char resultTarFile[512];
	snprintf(tarFile, sizeof(tarFile), "/tmp/%s.tar", filename);
	snprintf(resultTarFile, sizeof(resultTarFile), "/tmp/%s-result.tar", filename);

	// Clean up both tar files individually
	for (int i = 0; i < 2; i++) {
		const char* fileToDelete = (i == 0) ? tarFile : resultTarFile;

		struct stat st;
		if (stat(fileToDelete, &st) == 0) {
			vlog.debug("%s: Removing tar file: %s (size: %ld bytes)",
					   __func__, fileToDelete, (long)st.st_size);

			if (remove(fileToDelete) == 0) {
				vlog.debug("%s: Successfully removed: %s", __func__, fileToDelete);
			} else {
				vlog.error("%s: Failed to remove %s: %s",
						   __func__, fileToDelete, strerror(errno));
			}
		} else {
			vlog.debug("%s: Tar file does not exist (already cleaned?): %s",
					   __func__, fileToDelete);
		}
	}
}

[[deprecated("Not using anymore - it works, but slows down login unnecessarily")]]
std::vector<std::string> getAvailableScannersFromVaithex(const char* caCertPath) {
	std::vector<std::string> availableScanners;

	char sysinfoCmd[1500];
	snprintf(sysinfoCmd, sizeof(sysinfoCmd),
			 "wget --ca-certificate=\"%s\" --quiet --output-document=- "
			 "'https://%s:%s/v1/sysinfo'",
		  caCertPath, vaithexIp.getData(), vaithexPort.getData());

	vlog.debug("%s: Fetching available scanners: %s", __func__, sysinfoCmd);

	FILE* sysinfoStream = popen(sysinfoCmd, "r");
	if (!sysinfoStream) {
		vlog.error("%s: Failed to execute sysinfo command", __func__);
		return availableScanners;
	}

	char response[8192];
	size_t responseSize = fread(response, 1, sizeof(response) - 1, sysinfoStream);
	pclose(sysinfoStream);

	if (responseSize == 0) {
		vlog.error("%s: Empty response from sysinfo endpoint", __func__);
		return availableScanners;
	}
	response[responseSize] = '\0';

	vlog.debug("%s: Sysinfo response: %s", __func__, response);

	try {
		json sysinfoJson = json::parse(response);

		if (sysinfoJson.contains("scanners")) {
			for (json::iterator it = sysinfoJson["scanners"].begin();
				 it != sysinfoJson["scanners"].end(); ++it) {
				std::string scannerName = it.key();
			json scannerInfo = it.value();

			bool installed = scannerInfo.value("installed", false);
			bool available = scannerInfo.value("available", false);

			if (installed && available) {
				availableScanners.push_back(scannerName);
				vlog.debug("%s: Found available scanner: %s", __func__, scannerName.c_str());
			}
				 }
		}
	} catch (const json::parse_error& e) {
		vlog.error("%s: JSON parsing error: %s", __func__, e.what());
	}

	return availableScanners;
}

[[deprecated("Not using anymore - it works, but slows down login unnecessarily")]]
void initializeVaithexScanners() {
	const char* caCertPath = "/home/user/.customvaithexca/ca-certificates";

	// Fetch available scanners from the VAITHEX server
	std::vector<std::string> availableScanners = getAvailableScannersFromVaithex(caCertPath);

	if (!availableScanners.empty()) {
		// Display scanners returned from the VAITHEX server
		std::string serverScannersList;
		for (size_t i = 0; i < availableScanners.size(); i++) {
			if (i > 0) serverScannersList += ",";
			serverScannersList += availableScanners[i];
		}
		vlog.info("%s: Scanners available from VAITHEX server: %s",
				  __func__, serverScannersList.c_str());

		// Log the currently configured scanners (from user config)
		if (strlen(vaithexScanners.getData()) == 0) {
			vlog.info("%s: No scanners configured by user",
					  __func__);
		} else {
			vlog.info("%s: User-configured scanners: %s",
					  __func__, vaithexScanners.getData());
		}
	} else {
		vlog.error("%s: No scanners detected from the VAITHEX server", __func__);
	}
}

// Static flag to ensure cleanup runs only once per process
static bool g_loginCleanupDone = false;

SAutotransferVaithex::SAutotransferVaithex() {
	vlog.debug("%s: Setting timeout for processFile calls to %d seconds", __func__, (int) vaithexTimeout);
	setProcessFileTimeout((int) vaithexTimeout);
	setLogPathForEvents("/home/user/.vaithexlog/" + getEffectiveUser()); // For mp-watch

	mimeChecker = new SAutotransferMime();
	// Initialize scanners from Vaithex if not already configured
	// initializeVaithexScanners();

	if (!g_loginCleanupDone) {
		vlog.info("%s: Running one-time login cleanup for user %s (PID: %d)",
				  __func__, getenv("USER"), getpid());

		// Clean up old /tmp/vaithex-tmp-* directories
		vlog.info("%s: Scanning for old /tmp/vaithex-tmp-* directories", __func__);

		DIR* tmpDir = opendir("/tmp");
		if (tmpDir) {
			struct dirent* entry;
			int cleanedCount = 0;

			while ((entry = readdir(tmpDir)) != NULL) {
				if (strncmp(entry->d_name, "vaithex-tmp-", 12) == 0) {
					char fullPath[512];
					snprintf(fullPath, sizeof(fullPath), "/tmp/%s", entry->d_name);
					// Extract PID from directory name
					pid_t dirPid = atoi(entry->d_name + 12);
					// Check if this is our current temp directory
					if (dirPid == getpid()) {
						vlog.debug("%s: Skipping current process temp dir: %s", __func__, fullPath);
						continue;
					}
					// Check if the process is still running
					if (kill(dirPid, 0) == 0) {
						vlog.debug("%s: Skipping %s - process %d is still running",
								   __func__, fullPath, dirPid);
					} else if (errno == ESRCH) {
						// Process doesn't exist, safe to delete
						vlog.info("%s: Deleting stale directory: %s (PID %d not running)",
								  __func__, fullPath, dirPid);
						if (removeDirectory(fullPath) == 0) {
							cleanedCount++;
						}
					}
				}
			}
			closedir(tmpDir);
			vlog.info("%s: Cleaned %d stale vaithex-tmp directories",
					  __func__, cleanedCount);
		} else {
			vlog.error("%s:  Failed to open /tmp directory: %s",
					   __func__, strerror(errno));
		}
		//  Clean up old session directories
		//vlog.info("%s: Cleaning old session directories", __func__);
		// Clean up old tar files
		vlog.info("%s: Cleaning old tar files from /tmp", __func__);

		DIR* tmpDirForTars = opendir("/tmp");
		if (tmpDirForTars) {
			struct dirent* entry;
			int removedCount = 0;
			time_t now = time(NULL);

			while ((entry = readdir(tmpDirForTars)) != NULL) {
				if (entry->d_type != DT_REG) continue;
				// Check if it's a tar file
				size_t len = strlen(entry->d_name);
				if (len < 4) continue;

				bool isTarFile = (strcmp(entry->d_name + len - 4, ".tar") == 0);
				bool isResultTar = (len > 11 && strcmp(entry->d_name + len - 11, "-result.tar") == 0);

				if (isTarFile || isResultTar) {
					char fullPath[512];
					snprintf(fullPath, sizeof(fullPath), "/tmp/%s", entry->d_name);

					struct stat st;
					if (stat(fullPath, &st) == 0) {
						// Only delete files older than 1 day
						double ageHours = difftime(now, st.st_mtime) / 3600.0;
						if (ageHours > 24.0) {
							vlog.debug("%s: Removing old tar file: %s (age: %.1f hours)",
									   __func__, fullPath, ageHours);
							if (remove(fullPath) == 0) {
								removedCount++;
							}
						}
					}
				}
			}
			closedir(tmpDirForTars);
			vlog.info("%s: [CLEANUP 3/3] Removed %d old tar files", __func__, removedCount);
		}

		vlog.info("%s: Login cleanup completed", __func__);
		g_loginCleanupDone = true;
	}
}

char* listFirstFilePathInDir(const char* dirName) {
	DIR* dir;
	struct dirent* entry;

	dir = opendir(dirName);

	if (dir == NULL) {
		vlog.error("%s: failed to open %s", __func__, dirName);
		return NULL;
	}

	char* firstFilePath = NULL;
	entry = readdir(dir);

	if (entry != NULL) {
		if (entry->d_type == DT_REG) {
			firstFilePath = (char*)malloc(strlen(dirName) + 1 + strlen(entry->d_name) + 1);
			if (firstFilePath != NULL) {
				sprintf(firstFilePath, "%s/%s", dirName, entry->d_name);
			}
		}
	}

	closedir(dir);
	return firstFilePath;
}

char* getVaithexScannersAsJsonStr() {
	if (!vaithexScannersAsJsonStr) {
		const char* scanners = vaithexScanners.getData();
		vlog.debug("%s: vaithexScanners parameter value: '%s'", __func__, scanners);

		// Check if scanners is empty or not configured
		if (strlen(scanners) == 0) {
			vlog.info("%s: No scanners configured, returning empty array", __func__);
			vaithexScannersAsJsonStr = strdup("[]");
		} else {
			// Parse the comma-separated scanner list
			char* input = strdup(scanners);
			vlog.debug("%s: Parsing scanner list: %s", __func__, input);

			// Build JSON array from configured scanners
			json scannersArray = json::array();
			char* token = strtok((char*) input, ",");

			while (token != NULL) {
				// Trim whitespace
				while (*token == ' ' || *token == '\t') token++;
				char* end = token + strlen(token) - 1;
				while (end > token && (*end == ' ' || *end == '\t')) end--;
				*(end + 1) = '\0';

				if (strlen(token) > 0) {
					scannersArray.push_back(token);
					vlog.debug("%s: Added scanner: %s", __func__, token);
				}

				token = strtok(NULL, ",");
			}

			free(input);
			vaithexScannersAsJsonStr = strdup(scannersArray.dump().c_str());
			vlog.info("%s: Using configured scanners: %s", __func__, vaithexScannersAsJsonStr);
		}
	}
	return vaithexScannersAsJsonStr;
}

void logVaithexError(const char* filename, const char* errorMessage) {
	char hostname[HOST_NAME_MAX];
	gethostname(hostname, HOST_NAME_MAX);

	char logpath[128];
	snprintf(logpath, 127, "/home/user/.vaithexlog/%s", getenv("USER"));
	logpath[127] = 0;

	if (access(logpath, F_OK) != 0) {
		vlog.error("%s: logfile %s not found, cannot log error", __func__, logpath);
		return;
	}

	FILE *handle = fopen(logpath, "a");
	if (!handle) {
		vlog.error("%s: failed to open logfile %s: %s", __func__, logpath, strerror(errno));
		return;
	}

	time_t nowsec = time(NULL);
	struct tm *now = localtime(&nowsec);

	char *line = (char *)malloc(4096);
	if (line) {
		// Format: hostname date time filename ERROR: message
		snprintf(line, 4095, "%s %02u.%02u.%u %02u:%02u:%02u %s ERROR: %s\n",
				 hostname, now->tm_mday, now->tm_mon+1, now->tm_year+1900,
		   now->tm_hour, now->tm_min, now->tm_sec,
		   filename, errorMessage);

		vlog.debug("%s: logging error '%s' to %s", __func__, line, logpath);
		fwrite(line, 1, strlen(line), handle);

		if (ferror(handle)) {
			vlog.error("%s: failed to write to logfile '%s'", __func__, logpath);
		} else {
			system("/usr/bin/pkill inotifywait");  // Trigger notification
		}

		free(line);
	}

	fclose(handle);
}

string SAutotransferVaithex::getDirToWatch() const
{
	return "/home/user/.transfer/" + getEffectiveUser() + "/vaithex";
}

void checkCACertificate(const char* caCertPath) {
	if (access(caCertPath, F_OK) != -1) {
		vlog.debug("%s: CA certificate file found at %s", __func__, caCertPath);
	} else {
		vlog.error("%s: CA certificate file not found at %s", __func__, caCertPath);
		exit(EXIT_FAILURE);
	}
}

bool createJob(char* jobIdOutput, char* tokenOutput, const char* caCertPath) {
	const char* scanners = getVaithexScannersAsJsonStr();
	if (strlen(scanners) == 0) {
		snprintf(jobsPostBody, sizeof(jobsPostBody), "{\"normalize\": true,\"scanners_enabled\": [],\"server_timeout_sec\":0}");
	} else {
		snprintf(jobsPostBody, sizeof(jobsPostBody), "{\"normalize\": true,\"scanners_enabled\": %s,\"server_timeout_sec\":0}", scanners);
	}

	checkCACertificate(caCertPath);

	// Build the wget command with the CA certificate
	char getJobCmd[1500];
	snprintf(getJobCmd, sizeof(getJobCmd),"wget --ca-certificate=\"%s\" --quiet --progress=dot --method=POST --output-document=- --body-data='%s' 'https://%s:%s/v1/jobs'", caCertPath, jobsPostBody, vaithexIp.getData(), vaithexPort.getData());

	vlog.debug("%s: running command: %s", __func__, getJobCmd);
	FILE* responseStream = popen(getJobCmd, "r");
	if (!responseStream) {
		vlog.error("%s: Failed to execute command", __func__);
		return false;
	}

	char response_buffer[4096];
	size_t responseSize = fread(response_buffer, 1, sizeof(response_buffer) - 1, responseStream);
	if (responseSize == (size_t)-1) {
		vlog.error("%s: Failed to read response from the stream", __func__);
		pclose(responseStream);
		return false;
	}
	pclose(responseStream);
	if (responseSize == 0) {
		vlog.error("%s: Empty response from the server", __func__);
		return false;
	}
	response_buffer[responseSize] = '\0';

	vlog.debug("Response from server: %s", response_buffer);

	try {
		json response_json = json::parse(response_buffer);

		if (response_json.contains("id") && response_json.contains("token")) {
			job_id = response_json["id"];
			token = response_json["token"];
			snprintf(jobIdOutput, 4096, "%ld", job_id);
			snprintf(tokenOutput, 4096, "%s", token.c_str());
			vlog.debug("%s: Job ID: %ld, Token: %s", __func__, job_id, token.c_str());
		} else {
			vlog.error("%s: JSON response is missing 'id' or 'token' fields", __func__);
			return false;
		}
	} catch (const json::parse_error& e) {
		vlog.error("%s: JSON parsing error: %s", __func__, e.what());
		return false;
	}

	return true;
}

void removeNonPrintableCharacters(char* str, size_t size) {
	int len = strlen(str);
	int i, j = 0;
	for (i = 0; i < len; i++) {
		if (isprint(str[i])) {
			str[j++] = str[i];
		}
	}
	str[j] = '\0';
}

// Returns bool to indicate success/failure
bool uploadPayload(char* jobIdOutput, char* tokenOutput, const char* filename, const char* dir, const char* caCertPath) {
	vlog.debug("%s: Uploading payload for filename: %s", __func__, filename);

	char uploadUrl[600];
	snprintf(uploadUrl, sizeof(uploadUrl), "https://%s:%s/v1/jobs/%ld/payload", vaithexIp.getData(), vaithexPort.getData(), job_id);

	// Create the tar file with proper quoting for spaces
	char tar_cmd[700];
	snprintf(tar_cmd, sizeof(tar_cmd), "tar -cf \"/tmp/%s.tar\" -C \"%s\" \"%s\"", filename, dir, filename);

	vlog.info("%s: Executing tar cmd: %s", __func__, tar_cmd);
	int tarResult = system(tar_cmd);
	if (tarResult != 0) {
		vlog.error("%s: Failed to create tar file for %s (exit code: %d)", __func__, filename, tarResult);
		free(jobIdOutput);
		free(tokenOutput);
		return false;
	}
	// Upload
	char uploadCmd[1500];
	snprintf(uploadCmd, sizeof(uploadCmd), "wget --ca-certificate=\"%s\" --quiet --progress=dot --method=POST --output-document=- --body-file=\"/tmp/%s.tar\" --header='X-Waas-Token: %s' '%s'", caCertPath, filename, tokenOutput, uploadUrl);

	vlog.debug("%s: Executing wget cmd for uploadPayload: %s", __func__, uploadCmd);
	int uploadResult = system(uploadCmd);

	if (uploadResult == 0) {
		vlog.debug("%s: Payload uploaded for filename: %s", __func__, filename);
	} else {
		vlog.error("%s: Payload upload failed for filename: %s. Error code: %d", __func__, filename, uploadResult);

		// Clean up tar file before returning
		char cleanupCmd[1000];
		snprintf(cleanupCmd, sizeof(cleanupCmd), "rm -f \"/tmp/%s.tar\"", filename);
		system(cleanupCmd);

		free(jobIdOutput);
		free(tokenOutput);
		return false;
	}
	vlog.debug("Uploaded tar file name: %s.tar", filename);

	// Clean up the temporary tarball file
	char cleanupCmd[1000];
	snprintf(cleanupCmd, sizeof(cleanupCmd), "rm -f \"/tmp/%s.tar\"", filename);
	system(cleanupCmd);

	free(jobIdOutput);
	free(tokenOutput);
	return true;
}

// Returns bool to indicate success/failure
bool fetchMetadata(const char* token, long job_id, char* metadata_json_buffer, const char* caCertPath) {
	char metadata_url[600];
	snprintf(metadata_url, sizeof(metadata_url), "https://%s:%s/v1/jobs/%ld", vaithexIp.getData(), vaithexPort.getData(), job_id);

	char metadata_cmd[1000];
	snprintf(metadata_cmd, sizeof(metadata_cmd), "wget --ca-certificate=\"%s\" --quiet --method=GET --output-document=- --header='X-Waas-Token: %s' '%s'", caCertPath, token, metadata_url);

	vlog.debug("%s: Executing the wget metadata cmd: %s", __func__, metadata_cmd);

	FILE* metadata_output = popen(metadata_cmd, "r");
	if (metadata_output == NULL) {
		vlog.error("Failed to execute the metadata cmd: %s", strerror(errno));
		return false;
	}

	char metadata_response[4096];
	if (fgets(metadata_response, sizeof(metadata_response), metadata_output) == NULL) {
		vlog.error("Failed to read metadata response: %s", strerror(errno));
		pclose(metadata_output);
		return false;
	}
	pclose(metadata_output);

	vlog.debug("%s: Complete metadata response: %s", __func__, metadata_response);

	removeNonPrintableCharacters(metadata_response, sizeof(metadata_response));

	json parsed_metadata_json;
	try {
		parsed_metadata_json = json::parse(metadata_response);
	} catch (const json::parse_error &e) {
		vlog.error("JSON parsing error: %s", e.what());
		return false;
	}

	snprintf(metadata_json_buffer, 4096, "%s", parsed_metadata_json.dump().c_str());
	return true;
}

const char* outputDir = "/tmp/vaithex-tmp";

void processCleanOrNormalized(const char* filePath, const char* filename, const char* metadata_response, const char* caCertPath) {
	json metadata_json;
	char* tmpVaithexDir = NULL;

	try {
		metadata_json = json::parse(metadata_response);
	} catch (const json::parse_error &e) {
		vlog.error("JSON parsing error: %s", e.what());
		vlog.error("Metadata response that caused the error: %s", metadata_response);
		return;
	}

	if (metadata_json.contains("result_report") && metadata_json["result_report"].contains("result_worst")) {
		std::string result_worst = metadata_json["result_report"]["result_worst"];
		if (result_worst == "clean") {
			vlog.debug("Result report: clean (could also be normalized)");
			if (strstr(metadata_response, "\"normalized_state\":\"successful\"") == nullptr) {
				std::string autotransferNowFile = "/home/user/.autotransfer-now/" + std::string(getpwuid(geteuid())->pw_name) + "/" + filename;
				vlog.debug("%s: trying to mp_move from '%s' to '%s'", __func__, filePath, autotransferNowFile.c_str());
				char* nonConstFilePath = strdup(filePath);
				mp_move(nonConstFilePath, const_cast<char*>(autotransferNowFile.c_str()), 1);
				free(nonConstFilePath);
				vlog.debug("%s: moved '%s' to '%s'", __func__, filePath, autotransferNowFile.c_str());
			} else {
				vlog.debug("File was normalized (actually only check if \"normalized_state\":\"successful\" is in the response at all)");
				vlog.debug("%s: removing %s because it was normalized. Will auto-transfer the normalized file to the client.", __func__, filePath);
				char* nonConstFilePath = strdup(filePath);
				delete_file(nonConstFilePath);
				free(nonConstFilePath);

				char result_payload_url[600];
				snprintf(result_payload_url, sizeof(result_payload_url), "https://%s:%s/v1/jobs/%ld/payload", vaithexIp.getData(), vaithexPort.getData(), job_id);
				vlog.error("Result Payload URL: %s", result_payload_url);

				char tarredResult[1024];
				snprintf(tarredResult, sizeof(tarredResult), "/tmp/%s-result.tar", filename);
				vlog.debug("Value of tarredResult: %s", tarredResult);

				// Download result payload with proper quoting
				char result_payload_cmd[4096];
				snprintf(result_payload_cmd, sizeof(result_payload_cmd),
						 "wget --ca-certificate=\"%s\" --quiet --progress=dot --method=GET --output-document=\"%s\" --header='X-Waas-Token: %s' '%s'",
			 caCertPath, tarredResult, token.c_str(), result_payload_url);
				vlog.debug("Running: %s", result_payload_cmd);
				if (system(result_payload_cmd) == 0) {
					vlog.debug("Result (was cleaned if necessary) archived in %s", tarredResult);
					vlog.debug("Tarred Result Path: %s", tarredResult);

					tmpVaithexDir = (char*)malloc(256);
					snprintf(tmpVaithexDir, 256, "/tmp/vaithex-tmp-%d", getpid());
					if (mkdir(tmpVaithexDir, 0700) && errno != EEXIST) {
						vlog.error("Failed to create a tgpro temporary directory for autotransfer.");
						free(tmpVaithexDir);
						return;
					}

					char* extractedFile = (char*) malloc(4096);
					int untarResult = extractAll(tarredResult, tmpVaithexDir, extractedFile);
					if (untarResult != 0) {
						vlog.error("Failed to untar the downloaded payload.");
						free(extractedFile);
						cleanupTempDir(tmpVaithexDir);
						free(tmpVaithexDir);
						return;
					}

					if (!extractedFile || !*extractedFile) {
						vlog.error("%s: failed to get name of normalized file in %s", __func__, tmpVaithexDir);
						free(extractedFile);
						cleanupTempDir(tmpVaithexDir);
						free(tmpVaithexDir);
						return;
					}

					vlog.debug("%s: extracted normalized file to %s", __func__, extractedFile);
					char* normalizedFilename = basename(strdup(extractedFile));
					vlog.debug("%s: normalized file's filename: %s", __func__, normalizedFilename);
					if (!normalizedFilename) {
						vlog.error("%s: failed to get filename for file '%s'. Aborting...", __func__, extractedFile);
						free(extractedFile);
						cleanupTempDir(tmpVaithexDir);
						free(tmpVaithexDir);
						return;
					}

					// Append "___vaithexed" before extension
					std::string modifiedFilename = std::string(normalizedFilename);
					size_t dotPos = modifiedFilename.find_last_of('.');
					if (dotPos != std::string::npos) {
						modifiedFilename.insert(dotPos, "___vaithexed");
					} else {
						modifiedFilename += "___vaithexed";
					}
					vlog.debug("Filename %s has been modified to: %s", normalizedFilename, modifiedFilename.c_str());

					std::string autotransferNowFile = "/home/user/.autotransfer-now/" + std::string(getpwuid(geteuid())->pw_name) + "/" + modifiedFilename;
					vlog.debug("%s: trying to mp_move '%s' to '%s'", __func__, extractedFile, autotransferNowFile.c_str());
					vlog.debug("Autotransfer will move file: %s to path: %s", extractedFile, autotransferNowFile.c_str());
					char* nonConstExtractedFile = strdup(extractedFile);
					mp_move(nonConstExtractedFile, const_cast<char*>(autotransferNowFile.c_str()), 1);
					free(nonConstExtractedFile);
					vlog.debug("Autotransfer completed successfully for file: %s at path: %s", modifiedFilename.c_str(), autotransferNowFile.c_str());

					// CLEANUP: Only local temp directory (caller handles tar cleanup)
					free(extractedFile);
					cleanupTempDir(tmpVaithexDir);
					free(tmpVaithexDir);
				} else {
					vlog.error("Failed to download result payload!");
					return;
				}
			}
		}
	}
}

void processInfected(const char* filePath, const char* filename, const char* metadata_response) {
	json metadata_json;
	try {
		metadata_json = json::parse(metadata_response);
	} catch (const json::parse_error &e) {
		vlog.error("JSON parsing error: %s", e.what());
		vlog.error("Metadata response that caused the error: %s", metadata_response);
		return;
	}

	if (metadata_json.contains("result_report") && metadata_json["result_report"].contains("result_worst")) {
		std::string result_worst = metadata_json["result_report"]["result_worst"];

		if (result_worst == "infected") {
			vlog.debug("Result report: Blocked!");

			char hostname[HOST_NAME_MAX];
			gethostname(hostname, HOST_NAME_MAX);

			char logpath[128];

			delete_file((char*) filePath);
			vlog.info("%s: VAITHEX rejects %s, scan result: %s", __func__, filename, metadata_response ? metadata_response : "Unknown");

			#if !defined(WIN32) && !defined(WIN64)
			openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
			syslog(LOG_DEBUG, "%s: VAITHEX rejects %s, scan result: %s", __func__, filename, metadata_response);
			#endif

			snprintf(logpath, 127, "/home/user/.vaithexlog/%s", getenv("USER"));
			logpath[127] = 0;
			if (!access(logpath, F_OK)) {
				FILE *handle;

				handle = fopen(logpath, "a");
				if (handle) {
					char * line = (char *) malloc(4096);

					if (line) {
						time_t nowsec = time(NULL);
						struct tm * now = localtime(&nowsec);
						// "INFECTED IS BLOCKED"
						snprintf(line, 4095, "%s %02u.%02u.%u %02u:%02u:%02u %s %s\n", hostname, now->tm_mday, now->tm_mon+1, now->tm_year+1900, now->tm_hour, now->tm_min, now->tm_sec, filename, filePath);

						vlog.debug("%s: logging '%s' to %s", __func__, line, logpath);
						fwrite(line, 1, strlen(line), handle);
						if (ferror(handle)) {
							fclose(handle);
							vlog.error("%s: failed to write file '%s'", __func__, logpath);
							#if !defined(WIN32) && !defined(WIN64)
							openlog("Xtightgatevnc", LOG_PID, LOG_AUTH);
							syslog(LOG_DEBUG, "%s: failed to write file '%s'", __func__, logpath);
							#endif
						} else {
							fclose(handle);
							system("/usr/bin/pkill inotifywait");
						}
						free(line);
					}
				} else {
					vlog.error("%s: failed to open logfile %s, not logging, error: %s (notifications with mp-watch won't work!)", __func__, logpath, strerror(errno));
				}
			} else {
				vlog.error("%s: logfile %s not found, not logging (notifications with mp-watch won't work!)", __func__, logpath);
			}

			return;
		}
	} else {
		vlog.error("%s: Error! wget's response size is 0 or smaller", __func__);
		return;
	}
}

static int extractAll(const char* filename, const char* outputDir, char* lastExtractedFileOutput) {
	vlog.debug("%s: extracting all files from '%s' to '%s'", __func__, filename, outputDir);

	// Extract with proper quoting for spaces
	char untarCmd[4096];
	snprintf(untarCmd, sizeof(untarCmd), "tar -xf \"%s\" -C \"%s\"", filename, outputDir);

	vlog.debug("%s: executing: %s", __func__, untarCmd);

	int untarResult = system(untarCmd);

	if (untarResult != 0) {
		vlog.error("%s: Failed to untar the downloaded payload. Command: %s, Exit code: %d",
				   __func__, untarCmd, untarResult);
		return 1;
	}

	DIR *dir;
	struct dirent *ent;
	char *lastFile = NULL;
	if ((dir = opendir(outputDir)) != NULL) {
		while ((ent = readdir(dir)) != NULL) {
			if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) {
				free(lastFile);
				lastFile = strdup(ent->d_name);
			}
		}
		closedir(dir);
	} else {
		vlog.error("%s: Unable to open directory '%s'.", __func__, outputDir);
		return 1;
	}

	if (lastFile != NULL) {
		snprintf(lastExtractedFileOutput, PATH_MAX, "%s/%s", outputDir, lastFile);
		free(lastFile);
	} else {
		vlog.error("%s: No files extracted from the tarball.", __func__);
		return 1;
	}

	return 0;
}

void SAutotransferVaithex::processFile(char *filePath)
{
	char* filename = basename(strdup(filePath));
	char* dir = dirname(strdup(filePath));
	vlog.debug("%s: VAITHEXing file '%s' (filename '%s')", __func__, filePath, filename);

	if (mimeChecker && !mimeChecker->checkFile(filePath)) {
		vlog.info("%s: File '%s' rejected by MIME type check, skipping VAITHEX", __func__, filename);
		logEvent(filename, "ERROR: MIME_TYPE_REJECTION_ERROR");
		return;
	}
	char* jobIdOutput = (char*) malloc(4096);
	char* tokenOutput = (char*) malloc(4096);

	const char* caCertPath = "/home/user/.customvaithexca/ca-certificates";
	// Check return value of createJob
	bool jobCreationSucceeded = createJob(jobIdOutput, tokenOutput, caCertPath);
	if (!jobCreationSucceeded) {
		vlog.error("%s: failed to create a job. Aborting VAITHEXing of file '%s'", __func__, filename);
		logEvent(filename, "ERROR: SERVER_JOB_CREATION_ERROR");
		free(jobIdOutput);
		free(tokenOutput);
		return;
	}
	vlog.debug("Yay! Got me some id (%s) and some token (%s)", jobIdOutput, tokenOutput);

	// Check return value of uploadPayload
	bool uploadSucceeded = uploadPayload(jobIdOutput, tokenOutput, filename, dir, caCertPath);
	if (!uploadSucceeded) {
		vlog.error("%s: Upload failed for file '%s'", __func__, filename);
		logEvent(filename, "ERROR: SERVER_UPLOAD_ERROR");
		return;
	}

	char response_buffer[4096];
	size_t size = strlen(response_buffer);
	removeNonPrintableCharacters(response_buffer, size);

	// Check return value of fetchMetadata
	char metadataOutput[4096];
	bool metadataFetched = fetchMetadata(tokenOutput, job_id, metadataOutput, caCertPath);

	if (!metadataFetched || strlen(metadataOutput) == 0 || strstr(metadataOutput, "\"id\"") == NULL) {
		vlog.error("%s: Failed to fetch valid metadata for file '%s'", __func__, filename);
		logEvent(filename, "ERROR: SERVER_METADATA_DOWNLOAD_ERROR");
		free(jobIdOutput);
		free(tokenOutput);
		cleanupTempTarFiles(filename);
		return;
	}

	processCleanOrNormalized(filePath, filename, metadataOutput, caCertPath);

	char* autotransferNowFile = strdup(filePath);
	autotransferNowFile[4095] = '\0';

	processInfected(filePath, filename, metadataOutput);

	// Final cleanup
	cleanupTempTarFiles(filename);

	free(jobIdOutput);
	free(tokenOutput);

	vlog.debug("%s: Processing completed for file '%s'", __func__, filename);
	return;

}

#endif /* #ifndef WIN32 */
