#include "Utility.h"
#include <ctime>
#include <chrono>
#include <mutex>
#include <map>
#include <memory>

// Bounded wait when acquiring GetZipPathLock inside extractor/writer entry
// points. A genuine extract is seconds, but adversarial I/O (slow NAS,
// antivirus holding files, libzip hang on malformed archive, a crashed
// thread that never unwound the lock) can make it block indefinitely.
// 15 minutes is generous enough for large models on slow storage yet short
// enough that a service-wide hang turns into a localized load-failure
// visible in DebugView.
static constexpr auto kZipPathLockTimeout = std::chrono::minutes(15);

// Per-path mutex to serialize concurrent zip operations on the same target.
// Without this, two LabVIEW threads can race: one extracting a zip while
// another truncates/writes the same file, corrupting data and crashing LabVIEW.
// Scope is limited to the extractor / zip writer — once ExtractProtectedZipFile
// returns, the sidecar check makes subsequent calls fast no-ops, and readers
// (ORT / TRT session create) access stable files concurrently with no lock.
static std::mutex g_zipPathMapMutex;
static std::map<std::string, std::shared_ptr<std::recursive_timed_mutex>> g_zipPathLocks;

static std::shared_ptr<std::recursive_timed_mutex> GetZipPathLock(const std::string& path) {
	std::lock_guard<std::mutex> guard(g_zipPathMapMutex);
	auto& ptr = g_zipPathLocks[path];
	if (!ptr) ptr = std::make_shared<std::recursive_timed_mutex>();
	return ptr;
}

template <typename T>
T get_data(const boost::property_tree::ptree& pt, const std::string& key)
{
	T ret;
	if (boost::optional<T> data = pt.get_optional<T>(key))
	{
		ret = data.get();
	}
	else
	{
		return T();
		//throw std::runtime_error("Could not read the data from ptree: [key: " + key + "]");
	}	
	return ret;
}
// Convert file path to only the filename
std::string path_to_filename(std::string path) {
	return path.substr(path.find_last_of("/\\") + 1);
}
std::string GetMainFolderDir() {
	std::string loggerDir = "C:\\ProgramData\\ANSCENTER";
	if (!FolderExist(loggerDir)) {
		CreateDirectory(loggerDir);
	}
	return loggerDir;
}

std::string GetLoggerDir()
{
	std::string mainFolderDir = GetMainFolderDir();
#ifdef _WIN32
	const char pathSeparator = '\\'; // Windows uses backslash
#else
	const char pathSeparator = '/'; // Unix-like systems use forward slash
#endif
	std::string loggerDir = mainFolderDir+ pathSeparator+"Logs";
	if (!FolderExist(loggerDir)) {
		CreateDirectory(loggerDir);
	}
	return loggerDir;
}
std::string GetLicenseDir()
{
	std::string mainFolderDir = GetMainFolderDir();
#ifdef _WIN32
	const char pathSeparator = '\\'; // Windows uses backslash
#else
	const char pathSeparator = '/'; // Unix-like systems use forward slash
#endif
	std::string loggerDir = mainFolderDir + pathSeparator + "Licenses";
	if (!FolderExist(loggerDir)) {
		CreateDirectory(loggerDir);
	}
	return loggerDir;
}
std::wstring String2WString(const std::string input)
{
	auto w = fs::path(input).wstring();
	return w;
}
std::string WString2String(const std::wstring input)
{
	auto s = fs::path(input).string();
	return s;
}
std::string Dash2Underscore(std::string text)
{
	std::replace(text.begin(), text.end(), '-', '_');
	return text;
}
std::string Underscore2Dash(std::string text) {
	std::replace(text.begin(), text.end(), '_', '-');
	return text;
}
std::string Space2Underscore(std::string text) {
	std::replace(text.begin(), text.end(), ' ', '_');
	return text;
}

bool FileExist(const std::string& filePath) {
	return fs::exists(filePath) && fs::is_regular_file(filePath);
}
bool FolderExist(const std::string& folderPath) {
	return fs::is_directory(folderPath);
}
bool CreateDirectory(const std::string& folderPath) {
	try {
		fs::create_directory(folderPath);
		return true;

	}
	catch (const std::exception& ex) {
		std::cerr << "Creating directory: " << ex.what() << std::endl;
		return false;
	}
}
bool RenameFile(const std::string& oldFilePath, const std::string& newFilePath) {
	try {
		fs::rename(oldFilePath, newFilePath);
		return true;
	}
	catch (const std::exception& ex) {
		std::cerr << "Error renaming file: " << ex.what() << std::endl;
		return false;
	}
}
bool CopyFile(const std::string& sourceFilePath, const std::string& destinationFilePath) {
	try {
		fs::copy_file(sourceFilePath, destinationFilePath, fs::copy_options::overwrite_existing);
		return true;
	}
	catch (const std::exception& ex) {
		std::cerr << "Error copying file: " << ex.what() << std::endl;
		return false;
	}
}
void CopyFiles(const std::string& sourceFolder, const std::string& destinationFolder)
{
	for (const auto& entry : std::filesystem::directory_iterator(sourceFolder))
	{
		if (entry.is_regular_file())
		{
			std::filesystem::copy(entry.path(), destinationFolder / entry.path().filename());
		}
	}
}
bool DeleteFile(const std::string& filePath) {
	try {
		fs::remove(filePath);
		return true;
	}
	catch (const std::exception& ex) {
		std::cerr << "Error deleting file: " << ex.what() << std::endl;
		return false;
	}
}
std::string FindFilePathInFileList(const std::vector<std::string>& vec, const std::string& substring)
{
	auto it = std::find_if(vec.begin(), vec.end(), [&substring](const std::string& str) {
		return str.find(substring) != std::string::npos;
		});

	return (it != vec.end()) ? *it : "";
}
bool DeleteLicenseFile(const std::string& filePath) {
	try {
		fs::remove(filePath);
		return true;
	}
	catch (const std::exception& ex) {
		std::cerr << "Error deleting file: " << ex.what() << std::endl;
		return false;
	}
}
void ToLowerCase(std::string& str) {
	for (auto& c : str)
	{
		c = tolower(c);
	}
}

std::string toLower(const std::string& str) {
	std::string result = str;
	std::transform(result.begin(), result.end(), result.begin(), ::tolower);
	return result;
}


std::string GetFileExtension(const std::string& filePath) {
	fs::path path(filePath);
	std::string extensionFile= path.extension().string();
	ToLowerCase(extensionFile);
	return extensionFile;
}
std::vector<std::string> ListFilesInFolder(const std::string& folderPath) {
	std::vector<std::string> filePaths;
	try {
		for (const auto& entry : std::filesystem::directory_iterator(folderPath)) {
			if (entry.is_regular_file()) {
				std::string filePath = entry.path().string();
				std::string fileName = GetFileNameWithoutExtension(filePath);
				std::string _licenseKey, _activationKey, _validationData;
				if (ReadLicenseKeyFile(filePath, _licenseKey, _activationKey, _validationData)) {
					filePaths.push_back(entry.path().string());
				}
			}
		}
	}
	catch (const std::exception& ex) {
		std::cerr << "Error listing files in folder: " << ex.what() << std::endl;
	}

	return filePaths;
}
void DeleteFilesInFolderExcept(const std::string& folderPath, const std::vector<std::string>& filesToKeep) {
	try {
		for (const auto& entry : fs::directory_iterator(folderPath)) {
			if (entry.is_regular_file()) {
				std::string fileName = entry.path().filename().string();
				if (std::find(filesToKeep.begin(), filesToKeep.end(), fileName) == filesToKeep.end()) {
					// File is not in the list of files to keep; delete it.
					fs::remove(entry.path());
					std::cout << "Deleted: " << entry.path() << std::endl;
				}
			}
		}
	}
	catch (const std::exception& ex) {
		std::cerr << "Error deleting files in folder: " << ex.what() << std::endl;
	}
}
bool DeleteFolder(const std::string& folderPath) {
	try {
		fs::remove_all(folderPath);
		return true;
	}
	catch (const std::exception& ex) {
		std::cerr << "Error deleting folder: " << ex.what() << std::endl;
		return false;
	}
}
std::string GetFileNameWithoutExtension(const std::string& filePath) {
	fs::path path(filePath);
	return path.stem().string();
}
std::string ReadFileContent(std::string filePath) {
	std::ifstream ifs(filePath);
	return std::string((std::istreambuf_iterator<char>(ifs)),
		(std::istreambuf_iterator<char>()));
}
std::string ReadFileAsBinary(const std::string& filename) {
	std::ifstream file(filename, std::ios::binary);
	if (!file) {
		std::cerr << "Failed to open file: " << filename << std::endl;
		return "";  // Return an empty string to indicate failure
	}

	// Read the file content into a stringstream
	std::stringstream buffer;
	buffer << file.rdbuf();

	// Convert the stringstream to a string
	return buffer.str();
}
int  ReadAllBytes(std::string filePath, std::vector<char>& allDataBytes) {
	// Open the file in binary mode
	std::ifstream file(filePath, std::ios::binary);

	if (!file.is_open()) {
		std::cerr << "Failed to open file." << std::endl;
		return 1;
	}

	// Get the file size
	file.seekg(0, std::ios::end);
	std::streampos fileSize = file.tellg();
	file.seekg(0, std::ios::beg);

	// Create a vector to store the file data
	std::vector<char> fileData(fileSize);

	// Read the file into the vector
	if (!file.read(fileData.data(), fileSize)) {
		std::cerr << "Failed to read file." << std::endl;
		return 1;
	}
	allDataBytes = fileData;

	return 0;
}
// Functionto get parent folder path (C:\ParentFolder\FileName) -> C:\ParentFolder
std::string GetParentFolder(const std::string& filePath) {
	size_t lastSlashPos = filePath.find_last_of("/\\"); // Handle both '/' and '\'
	if (lastSlashPos != std::string::npos) {
		return filePath.substr(0, lastSlashPos);
	}
	return ""; // Return an empty string if there is no folder
}
// Function to extract the folder name from a file path or file name
std::string ExtractFolderName(const std::string& directoryPath) {
	std::filesystem::path path(directoryPath);
	return path.stem().string();
}
// Function to create a file path from a folder name and a file name
std::string CreateFilePath(const std::string& folderName, const std::string& fileName) {
#ifdef _WIN32
	const char pathSeparator = '\\'; // Windows uses backslash
#else
	const char pathSeparator = '/'; // Unix-like systems use forward slash
#endif
	std::string folder = folderName;
	if (!folder.empty() && folder.back() != pathSeparator) {
		folder += pathSeparator;
	}
	return folder + fileName;
}

std::filesystem::path GetSafeTempDirectory() {
	std::filesystem::path systemTemp = std::filesystem::temp_directory_path();
	std::filesystem::path fallbackTemp = "C:\\ProgramData\\Jh7O7nUe7vS\\";

	// Ensure fallback directory exists
	if (!std::filesystem::exists(fallbackTemp)) {
		try {
			std::filesystem::create_directories(fallbackTemp);
		}
		catch (const std::exception& e) {
			std::cerr << "Failed to create fallback temp directory: " << e.what() << std::endl;
		}
	}

	// Convert systemTemp to lowercase string for checking
	std::string systemTempStr = toLower(systemTemp.string());

	// Check if systemTemp does not exist or contains "c:\windows"
	if (!std::filesystem::exists(systemTemp) ||
		systemTempStr.find("windows") != std::string::npos) {
		return fallbackTemp;
	}

	return systemTemp;
}
std::string GenerateOutputFolder(std::string modelFilePath, std::string modelName, bool edgeDeviceModel) {
	// Get the path to the Temp folder
	std::filesystem::path tempFolderPath = GetSafeTempDirectory();
	std::string outputFolder = "";
	outputFolder.clear();
	if (std::filesystem::exists(tempFolderPath)) {
		std::string strTempPath = tempFolderPath.string();
#ifdef _WIN32
		const char pathSeparator = '\\'; // Windows uses backslash
#else
		const char pathSeparator = '/'; // Unix-like systems use forward slash
#endif
		std::string baseFolder = strTempPath + "Models";
		if(!FolderExist(baseFolder)) {
			CreateDirectory(baseFolder);
		}
		std::string EdgeModelsFolder = baseFolder + pathSeparator + "EdgeModels";
		if (!FolderExist(EdgeModelsFolder)) {
			CreateDirectory(EdgeModelsFolder);
		}
		std::string EngineModelsFolder = baseFolder + pathSeparator + "EngineModels";
		if (!FolderExist(EngineModelsFolder)) {
			CreateDirectory(EngineModelsFolder);
		}
		if(edgeDeviceModel)
			outputFolder = EdgeModelsFolder + pathSeparator + modelName;
		else
			outputFolder = EngineModelsFolder + pathSeparator + modelName;
		return outputFolder;
	}
	else {
		return outputFolder;
	}
}

bool ExtractPasswordProtectedZip(const std::string& zipFileName,
	const std::string& password,
	const std::string& modelName,
	std::string& outputFolder,
	bool edgeDeviceModel) {
	outputFolder = GenerateOutputFolder(zipFileName, modelName, edgeDeviceModel);
	return ExtractProtectedZipFile(zipFileName, password, modelName, outputFolder);
}


bool AddFileToZip(zip* archive, const char* filePath, const char* zipPath, const char* password) {
	struct zip_source* source = zip_source_file(archive, filePath, 0, 0);
	if (!source) {
		std::cerr << "Failed to add file '" << filePath << "' to ZIP archive." << std::endl;
		return false;
	}

	if (zip_file_add(archive, zipPath, source, ZIP_FL_OVERWRITE) == -1) {
		std::cerr << "Failed to add file '" << filePath << "' to ZIP archive." << std::endl;
		zip_source_free(source);
		return false;
	}

	// Get the index of the added file
	zip_int64_t fileIndex = zip_name_locate(archive, zipPath, 0);
	if (fileIndex < 0) {
		std::cerr << "Failed to locate file '" << zipPath << "' in ZIP archive." << std::endl;
		zip_source_free(source);
		return false;
	}

	// Set encryption for the added file
	if (zip_file_set_encryption(archive, fileIndex, ZIP_EM_AES_256, password) == -1) {
		std::cerr << "Failed to set encryption for file '" << zipPath << "' in ZIP archive." << std::endl;
		zip_source_free(source);
		return false;
	}

	return true;
}

bool AddFolderContentsToZip(zip* archive, const char* folderPath, const char* zipPath, const char* password) {
	try {
		fs::path dir(folderPath);

		if (!fs::exists(dir) || !fs::is_directory(dir)) {
			std::cerr << "Failed to open directory '" << folderPath << "'." << std::endl;
			return false;
		}

		for (const fs::directory_entry& entry : fs::directory_iterator(dir)) {
			std::string entryName = entry.path().filename().string();
			std::string entryPath = (zipPath && zipPath[0] != '\0') ? std::string(zipPath) + "/" + entryName : entryName;
			std::string filePath = folderPath + std::string("/") + entryName;

			if (fs::is_directory(entry.status())) {
				if (entryName != "." && entryName != "..") {
					if (!AddFolderContentsToZip(archive, filePath.c_str(), entryPath.c_str(), password)) {
						return false;
					}
				}
			}
			else {
				if (!AddFileToZip(archive, filePath.c_str(), entryPath.c_str(), password)) {
					return false;
				}
			}
		}
	}
	catch (const std::exception& ex) {
		std::cerr << "Error: " << ex.what() << std::endl;
		return false;
	}

	return true;
}

bool ZipFolderWithPassword(const char* folderPath, const char* zipFilePath, const char* password) {
	auto pathLock = GetZipPathLock(std::string(zipFilePath));
	ANS_DBG("Extract", "ZipFolderWithPassword: waiting on zip lock (%llds): %s",
	        (long long)std::chrono::duration_cast<std::chrono::seconds>(kZipPathLockTimeout).count(),
	        zipFilePath ? zipFilePath : "(null)");
	std::unique_lock<std::recursive_timed_mutex> zipGuard(*pathLock, std::defer_lock);
	if (!zipGuard.try_lock_for(kZipPathLockTimeout)) {
		ANS_DBG("Extract", "ZipFolderWithPassword: TIMEOUT acquiring zip lock for %s",
		        zipFilePath ? zipFilePath : "(null)");
		return false;
	}
	ANS_DBG("Extract", "ZipFolderWithPassword: zip lock acquired: %s",
	        zipFilePath ? zipFilePath : "(null)");

	zip* zipArchive;
	zip_flags_t flags = ZIP_CREATE | ZIP_TRUNCATE;
	//std::cout << "Open Zip File: " << zipFilePath << std::endl;
	zipArchive = zip_open(zipFilePath, flags, nullptr);
	if (!zipArchive) {
		std::cerr << "Failed to create ZIP archive." << std::endl;
		return false;
	}

	// Set a password for the ZIP file
	if (zip_set_default_password(zipArchive, password) == -1) {
		std::cerr << "Failed to set the password for the ZIP file." << std::endl;
		zip_close(zipArchive);
		return false;
	}

	// Add folder contents to the ZIP archive
	if (!AddFolderContentsToZip(zipArchive, folderPath, "", password)) {
		zip_close(zipArchive);
		return false;
	}

	// Save the ZIP archive to the file
	if (zip_close(zipArchive) == -1) {
		std::cerr << "Failed to save the ZIP archive to the file." << std::endl;
		return false;
	}

	return true;
}

std::string VectorToCommaSeparatedString(const std::vector<std::string>& inputVector) {
	std::string result;
	for (size_t i = 0; i < inputVector.size(); ++i) {
		result += inputVector[i];
		if (i < inputVector.size() - 1) {
			result += ",";
		}
	}
	return result;
}
std::string VectorToSeparatedString(const std::vector<std::string>& inputVector) {
	std::string result;
	for (size_t i = 0; i < inputVector.size(); ++i) {
		result += inputVector[i];
		if (i < inputVector.size() - 1) {
			result += ";";
		}
	}
	return result;
}
std::vector<std::string> Split(const std::string str, const std::string regex_str) {
	std::regex regexz(regex_str);
	return { std::sregex_token_iterator(str.begin(), str.end(), regexz, -1),
			std::sregex_token_iterator() };
}

std::vector<std::string> GetTrialLicenseKeyFiles(std::string registrationName) {
	std::vector < std::string> trialLicenseKeyFiles;
	std::string trialLicenseKeyFileName = registrationName + "_Trial.lic";
	if (CreateDirectory("C:\\ProgramData\\Apple")) {
		std::string trialLicenseKeyFile1 = CreateFilePath("C:\\ProgramData\\Apple", trialLicenseKeyFileName);
		trialLicenseKeyFiles.push_back(trialLicenseKeyFile1);
	}
	if (CreateDirectory("C:\\ProgramData\\Licenses")) {
		std::string trialLicenseKeyFile2 = CreateFilePath("C:\\ProgramData\\Licenses", trialLicenseKeyFileName);
		trialLicenseKeyFiles.push_back(trialLicenseKeyFile2);
	}
	if (CreateDirectory("C:\\ProgramData\\McAfee")) {
		std::string trialLicenseKeyFile3 = CreateFilePath("C:\\ProgramData\\McAfee", trialLicenseKeyFileName);
		trialLicenseKeyFiles.push_back(trialLicenseKeyFile3);
	}
	if (CreateDirectory("C:\\ProgramData\\NVIDIA")) {
		std::string trialLicenseKeyFile4 = CreateFilePath("C:\\ProgramData\\NVIDIA", trialLicenseKeyFileName);
		trialLicenseKeyFiles.push_back(trialLicenseKeyFile4);
	}
	if (CreateDirectory("C:\\ProgramData\\National Instruments")) {
		std::string trialLicenseKeyFile5 = CreateFilePath("C:\\ProgramData\\National Instruments", trialLicenseKeyFileName);
		trialLicenseKeyFiles.push_back(trialLicenseKeyFile5);
	}
	if (CreateDirectory("C:\\ProgramData\\Microsoft")) {
		std::string trialLicenseKeyFile6 = CreateFilePath("C:\\ProgramData\\Microsoft", trialLicenseKeyFileName);
		trialLicenseKeyFiles.push_back(trialLicenseKeyFile6);
	}
	return trialLicenseKeyFiles;
}
bool ReadLicenseKeyFile(std::string licenseKeyFilePath, std::string& licenseKey, std::string& activationKey, std::string& validationData) {
	try {
		if (!FileExist(licenseKeyFilePath))return false;
		boost::property_tree::ptree pt;
		boost::property_tree::read_json(licenseKeyFilePath, pt);

		auto itLicenseKey = pt.get_child("license_key");
		licenseKey = itLicenseKey.get_value<std::string>();

		auto itActivationKey = pt.get_child("activation_key");
		activationKey = itActivationKey.get_value<std::string>();

		auto itRegistrationName = pt.get_child("license_key_validation_data");
		validationData = itRegistrationName.get_value<std::string>();

		if ((licenseKey.empty()) ||
			(activationKey.empty()) ||
			(validationData.empty())) return false;
		return true;
	}
	catch (std::exception& e) {
		std::cout<<"Read License key"<< e.what()<< std::endl;
		return false;
	}
	
}
std::string GetTrialLicenseKey(std::string registrationName) {
	std::string trialLicenseKey;
	std::vector<std::string> trialLicenseKeys;
	trialLicenseKeys.clear();

	// Get all licenseKeys from vavious places
	std::vector < std::string> trialLicenseKeyFiles =GetTrialLicenseKeyFiles(registrationName);
	size_t trialSize = trialLicenseKeyFiles.size();
	for (size_t i = 0; i < trialSize; i++) {
		std::string trialLicenseFile = trialLicenseKeyFiles[i];
		if (FileExist(trialLicenseFile)) {
			std::string trialLicenseKey = ReadFileContent(trialLicenseFile);
			trialLicenseKeys.push_back(trialLicenseKey);
		}
	}
	sort(trialLicenseKeys.begin(), trialLicenseKeys.end());
	trialLicenseKeys.erase(unique(trialLicenseKeys.begin(), trialLicenseKeys.end()), trialLicenseKeys.end());

	size_t lSize = trialLicenseKeys.size();
	if (lSize == 1) trialLicenseKey = trialLicenseKeys[0];
	return trialLicenseKey;
}

bool UpdateTrialLicenseKey(std::string trialLicenseKey, std::string registrationName) {
	try {
		std::vector < std::string> trialLicenseKeyFiles = GetTrialLicenseKeyFiles(registrationName);
		size_t trialSize = trialLicenseKeyFiles.size();
		for (size_t i = 0; i < trialSize; i++) {
			try {
				std::string trialLicenseFile = trialLicenseKeyFiles[i];
				std::ofstream(trialLicenseFile) << trialLicenseKey;
			}
			catch (const std::exception& ex) {
				std::cerr << "Error: " << ex.what() << std::endl;
			}	
		}
		return true;
	}
	catch (const std::exception& ex) {
		std::cerr << "Error: " << ex.what() << std::endl;
		return false;
	}
}


bool RemoveTrialLicenseKey(std::string registrationName) {
	try {
		std::vector < std::string> trialLicenseKeyFiles = GetTrialLicenseKeyFiles(registrationName);
		size_t trialSize = trialLicenseKeyFiles.size();
		for (size_t i = 0; i < trialSize; i++) {
			try {
				DeleteFile(trialLicenseKeyFiles[i]);
			}
			catch (const std::exception& ex) {
				std::cerr << "Error: " << ex.what() << std::endl;
			}
		}
		return true;
	}
	catch (const std::exception& ex) {
		std::cerr << "Error: " << ex.what() << std::endl;
		return false;
	}
}
int  GetExpirationDate(std::string licenseData, int &productId) {
	if (licenseData.empty()) return -1;
	int remainDate = -1;
	std::stringstream ss;
	ss << licenseData;
	boost::property_tree::ptree pt;
	boost::property_tree::read_json(ss, pt);
	auto rootNode = pt.get_child("licenseData");
	auto childNode = rootNode.get_child("remaining_days");
	remainDate = childNode.get_value<int>();

	childNode = rootNode.get_child("valid_product_id");
	productId = childNode.get_value<int>();

	return remainDate;
}


std::string GetStringCurrentDateTime() {
	// Get the current time
	std::time_t t = std::time(nullptr);
	std::tm now = {}; // Initialize to all zeros
	localtime_s(&now, &t);
	 // Convert the time to a string
	std::stringstream ss;
	ss << std::put_time(&now, "%Y%m%d%H%M%S");
	std::string currentDateTime = ss.str();
	return currentDateTime;
}

std::string GetDateTimeString(const std::string& format) {
	// Get the current time
	std::time_t t = std::time(nullptr);
	std::tm now = {}; // Initialize to all zeros
	localtime_s(&now, &t);
	// Convert the time to a string
	std::stringstream ss;
	ss << std::put_time(&now, format.c_str());
	std::string currentDateTime = ss.str();
	return currentDateTime;
}

// For traing engine
//bool ExtractProtectedZipFile(const std::string& zipFileName, const std::string& password, const std::string& modelName, const std::string outputFolder) 
//{
//	int error;
//	if (!FileExist(zipFileName))return false;
//	zip_t* archive = zip_open(zipFileName.c_str(), ZIP_RDONLY, &error);
//	if (!archive) {
//		std::cerr << "Error opening ZIP archive: " << zip_strerror(archive) << std::endl;
//		return false;
//	}
//	try {
//		if (!password.empty()) {
//			if (zip_set_default_password(archive, password.c_str()) == -1) {
//				std::cerr << "Error setting password: " << zip_strerror(archive) << std::endl;
//				zip_close(archive);
//				return false;
//			}
//		}
//		int numEntries = (int)zip_get_num_entries(archive, 0);
//		for (int i = 0; i < numEntries; i++) {
//			zip_file_t* file = zip_fopen_index(archive, i, 0);
//			if (!file) {
//				std::cerr << "Error opening file in ZIP: " << zip_strerror(archive) << std::endl;	
//				zip_close(archive);
//				return false;
//			}
//			// Check if the folder exists
//			if (!std::filesystem::exists(outputFolder)) {
//				// Folder does not exist, so create it
//				try {
//					std::filesystem::create_directories(outputFolder);
//				}
//				catch (const std::exception& ex) {
//					std::cerr << "Error opening file in ZIP: " << ex.what() << std::endl;
//					zip_close(archive);
//					zip_fclose(file);
//					return false;
//				}
//			}
//			try {// otherwise extract to the output folder
//				const char* filename = zip_get_name(archive, i, 0);
//				std::string outputPath = CreateFilePath(outputFolder, filename);
//
//				// Check if filename ends with '/' indicating it's a directory
//				if (outputPath.back() == '/') {// Create the directory and its parents as needed
//					fs::create_directories(outputPath);
//				}
//				else {// Proceed to extract the file
//					std::ofstream output(outputPath, std::ios::binary);
//					if (!output.is_open()) {
//						zip_fclose(file);
//					}
//					else {
//						const size_t buffer_size = 4096;
//						unsigned char buffer[buffer_size];
//						zip_int64_t bytesRead;
//						while ((bytesRead = zip_fread(file, buffer, buffer_size)) > 0) {
//							output.write(reinterpret_cast<char*>(buffer), bytesRead);
//						}
//						zip_fclose(file);
//						output.close();
//					}
//				}
//			}
//			catch (const std::exception& ex) {
//				std::cerr << "Error creating output file: " << ex.what() << std::endl;
//			}
//		}
//	}
//	catch (...) {
//		zip_close(archive);
//		return false;
//	}
//	zip_close(archive);
//	return true;
//}

//bool ExtractProtectedZipFile(const std::string& zipFileName, const std::string& password, const std::string& modelName, const std::string outputFolder) {
//	int error;
//	if (!FileExist(zipFileName)) return false;
//
//	zip_t* archive = zip_open(zipFileName.c_str(), ZIP_RDONLY, &error);
//	if (!archive) {
//		std::cerr << "Error opening ZIP archive: " << zip_strerror(archive) << std::endl;
//		return false;
//	}
//
//	try {
//		if (!password.empty()) {
//			if (zip_set_default_password(archive, password.c_str()) == -1) {
//				std::cerr << "Error setting password: " << zip_strerror(archive) << std::endl;
//				zip_close(archive);
//				return false;
//			}
//		}
//
//		int numEntries = static_cast<int>(zip_get_num_entries(archive, 0));
//		for (int i = 0; i < numEntries; i++) {
//			zip_file_t* file = zip_fopen_index(archive, i, 0);
//			if (!file) {
//				std::cerr << "Error opening file in ZIP: " << zip_strerror(archive) << std::endl;
//				zip_close(archive);
//				return false;
//			}
//
//			// Check and create the output folder if necessary
//			if (!std::filesystem::exists(outputFolder)) {
//				try {
//					std::filesystem::create_directories(outputFolder);
//				}
//				catch (const std::exception& ex) {
//					std::cerr << "Error creating output directory: " << ex.what() << std::endl;
//					zip_fclose(file);
//					zip_close(archive);
//					return false;
//				}
//			}
//
//			try {
//				const char* filename = zip_get_name(archive, i, 0);
//				std::string outputPath = CreateFilePath(outputFolder, filename);
//
//				// Check if the path is a directory and create it if necessary
//				if (outputPath.back() == '/') {
//					std::filesystem::create_directories(outputPath);
//				}
//				else {
//					std::ofstream output(outputPath, std::ios::binary);
//					if (!output.is_open()) {
//						std::cerr << "Error creating output file: " << outputPath << std::endl;
//						zip_fclose(file);
//						zip_close(archive);
//						return false;
//					}
//
//					const size_t buffer_size = 4096;
//					unsigned char buffer[buffer_size];
//					zip_int64_t bytesRead;
//					while ((bytesRead = zip_fread(file, buffer, buffer_size)) > 0) {
//						output.write(reinterpret_cast<char*>(buffer), bytesRead);
//					}
//
//					output.close();
//				}
//
//				zip_fclose(file);
//			}
//			catch (const std::exception& ex) {
//				std::cerr << "Error creating output file: " << ex.what() << std::endl;
//				zip_fclose(file);
//				zip_close(archive);
//				return false;
//			}
//		}
//	}
//	catch (...) {
//		zip_close(archive);
//		return false;
//	}
//
//	zip_close(archive);
//	return true;
//}
bool ExtractProtectedZipFile(const std::string& zipFileName, const std::string& password, const std::string& modelName, const std::string outputFolder)
{
	auto pathLock = GetZipPathLock(outputFolder);
	ANS_DBG("Extract", "ExtractProtectedZipFile: waiting on zip lock (%llds): %s",
	        (long long)std::chrono::duration_cast<std::chrono::seconds>(kZipPathLockTimeout).count(),
	        outputFolder.c_str());
	std::unique_lock<std::recursive_timed_mutex> zipGuard(*pathLock, std::defer_lock);
	if (!zipGuard.try_lock_for(kZipPathLockTimeout)) {
		ANS_DBG("Extract", "ExtractProtectedZipFile: TIMEOUT acquiring zip lock for %s (zip=%s)",
		        outputFolder.c_str(), zipFileName.c_str());
		return false;
	}
	ANS_DBG("Extract", "ExtractProtectedZipFile: zip lock acquired: %s", outputFolder.c_str());

	int error;
	if (!FileExist(zipFileName))return false;

	// Idempotent fast-path: skip re-extraction when the target folder already
	// holds exactly the content of the current zip. Identity is tracked by a
	// small sidecar file (.ans_extract_info) written inside outputFolder after
	// every successful extract. The sidecar records three fields — zip path,
	// zip size, zip mtime — and we SKIP only if ALL match the current zip AND
	// the folder still has at least one non-empty file.
	//
	// Why sidecar and not just mtime? mtime alone breaks on:
	//   • downgrades — a reverted older zip has an older mtime, so "file mtime
	//     >= zip mtime" falsely reports fresh and we never re-extract.
	//   • preserved-timestamp copies (cp --preserve=timestamps, some installers,
	//     Git-LFS checkouts) — new-but-older-mtime zips look "not newer".
	// Size differs whenever content differs (libzip's CRC/compression shifts
	// bytes even for tiny payload changes), so size+mtime+path uniquely pins
	// the zip identity in practice.
	//
	// Any mismatch / missing sidecar / FS error → fall through to full extract,
	// which unconditionally rewrites the sidecar at the end. This means the
	// first run after deploying this patched binary will do one re-extract per
	// model to establish the sidecar; subsequent runs hit the fast path.
	const std::filesystem::path sidecarPath =
		std::filesystem::path(outputFolder) / ".ans_extract_info";

	ANS_DBG("Extract", "ExtractProtectedZipFile: zip=%s -> folder=%s",
	        zipFileName.c_str(), outputFolder.c_str());

	// Capture the current zip's identity up-front so both the fast-path check
	// and the post-extract sidecar write see consistent values.
	std::uintmax_t curZipSize = 0;
	long long      curZipMtime = 0;
	bool           zipStatOk   = false;
	try {
		curZipSize  = std::filesystem::file_size(zipFileName);
		curZipMtime = std::filesystem::last_write_time(zipFileName)
		                  .time_since_epoch().count();
		zipStatOk   = true;
	}
	catch (const std::exception& ex) {
		ANS_DBG("Extract",
		        "ExtractProtectedZipFile: failed to stat zip (%s); extracting anyway",
		        ex.what());
	}

	try {
		if (zipStatOk &&
			std::filesystem::exists(outputFolder) &&
			std::filesystem::is_directory(outputFolder) &&
			std::filesystem::exists(sidecarPath))
		{
			// Read sidecar: 3 lines — zipPath, zipSize, zipMtime.
			std::ifstream sf(sidecarPath);
			std::string   storedPath;
			std::uintmax_t storedSize  = 0;
			long long      storedMtime = 0;
			std::getline(sf, storedPath);
			sf >> storedSize >> storedMtime;
			if (sf.good() || sf.eof()) {
				// Sanity: folder must still contain at least one non-empty file
				// (defends against a sidecar that outlived its content, e.g. a
				// user manually wiping model files but leaving the sidecar).
				bool hasNonEmpty = false;
				size_t numFiles = 0;
				for (const auto& e : std::filesystem::directory_iterator(outputFolder)) {
					if (!e.is_regular_file()) continue;
					if (e.path().filename() == ".ans_extract_info") continue;
					++numFiles;
					std::error_code ec;
					if (e.file_size(ec) > 0 && !ec) { hasNonEmpty = true; break; }
				}
				const bool pathOk  = (storedPath  == zipFileName);
				const bool sizeOk  = (storedSize  == curZipSize);
				const bool mtimeOk = (storedMtime == curZipMtime);
				if (pathOk && sizeOk && mtimeOk && hasNonEmpty) {
					ANS_DBG("Extract",
					        "ExtractProtectedZipFile: SKIP re-extract — sidecar match (size=%llu mtime=%lld, %zu payload file(s))",
					        (unsigned long long)curZipSize,
					        (long long)curZipMtime, numFiles);
					return true;
				}
				ANS_DBG("Extract",
				        "ExtractProtectedZipFile: full extract needed — sidecar mismatch (path:%d size:%d mtime:%d nonEmpty:%d)",
				        pathOk ? 0 : 1, sizeOk ? 0 : 1, mtimeOk ? 0 : 1,
				        hasNonEmpty ? 1 : 0);
			} else {
				ANS_DBG("Extract",
				        "ExtractProtectedZipFile: sidecar unreadable, re-extracting");
			}
		} else if (zipStatOk) {
			ANS_DBG("Extract",
			        "ExtractProtectedZipFile: %s, full extract",
			        std::filesystem::exists(outputFolder) ? "no sidecar yet"
			                                              : "folder absent");
		}
	}
	catch (const std::exception& ex) {
		// Any filesystem hiccup: fall through to full extraction (safe).
		ANS_DBG("Extract",
		        "ExtractProtectedZipFile: freshness check threw, extracting: %s",
		        ex.what());
	}

	zip_t* archive = zip_open(zipFileName.c_str(), ZIP_RDONLY, &error);
	if (!archive) {
		std::cerr << "Error opening ZIP archive: " << zip_strerror(archive) << std::endl;
		return false;
	}
	try {
		if (!password.empty()) {
			if (zip_set_default_password(archive, password.c_str()) == -1) {
				std::cerr << "Error setting password: " << zip_strerror(archive) << std::endl;
				zip_close(archive);
				return false;
			}
		}
		int numEntries = (int)zip_get_num_entries(archive, 0);
		for (int i = 0; i < numEntries; i++) {
			zip_file_t* file = zip_fopen_index(archive, i, 0);
			if (!file) {
				std::cerr << "Error opening file in ZIP: " << zip_strerror(archive) << std::endl;
				zip_close(archive);
				return false;
			}
			// Check if the folder exists
			if (!std::filesystem::exists(outputFolder)) {
				// Folder does not exist, so create it
				try {
					std::filesystem::create_directories(outputFolder);
				}
				catch (const std::exception& ex) {
					std::cerr << "Error opening file in ZIP: " << ex.what() << std::endl;
					zip_close(archive);
					zip_fclose(file);
					return false;
				}
			}
			try {// otherwise extract to the output folder
				const char* filename = zip_get_name(archive, i, 0);
				std::string outputPath = CreateFilePath(outputFolder, filename);

				// Check if filename ends with '/' indicating it's a directory
				if (outputPath.back() == '/') {// Create the directory and its parents as needed
					fs::create_directories(outputPath);
				}
				else {// Proceed to extract the file
					std::ofstream output(outputPath, std::ios::binary);
					if (!output.is_open()) {
						zip_fclose(file);
					}
					else {
						const size_t buffer_size = 4096;
						unsigned char buffer[buffer_size];
						zip_int64_t bytesRead;
						while ((bytesRead = zip_fread(file, buffer, buffer_size)) > 0) {
							output.write(reinterpret_cast<char*>(buffer), bytesRead);
						}
						zip_fclose(file);
						output.close();
					}
				}
			}
			catch (const std::exception& ex) {
				std::cerr << "Error creating output file: " << ex.what() << std::endl;
			}
		}
	}
	catch (...) {
		zip_close(archive);
		return false;
	}
	zip_close(archive);

	// Extract succeeded — write the identity sidecar so the next call on this
	// folder can take the fast-path. We re-read zip stats here (rather than
	// trust the pre-extract snapshot) in case the zip was touched in between.
	try {
		std::uintmax_t finalSize  = std::filesystem::file_size(zipFileName);
		long long      finalMtime = std::filesystem::last_write_time(zipFileName)
		                                .time_since_epoch().count();
		std::ofstream sf(sidecarPath, std::ios::binary | std::ios::trunc);
		if (sf.is_open()) {
			sf << zipFileName << '\n' << finalSize << '\n' << finalMtime << '\n';
			ANS_DBG("Extract",
			        "ExtractProtectedZipFile: sidecar written (size=%llu mtime=%lld) at %s",
			        (unsigned long long)finalSize, (long long)finalMtime,
			        sidecarPath.string().c_str());
		} else {
			ANS_DBG("Extract",
			        "ExtractProtectedZipFile: could not open sidecar for write: %s",
			        sidecarPath.string().c_str());
		}
	}
	catch (const std::exception& ex) {
		// Non-fatal — extract already succeeded. Next call will just re-extract
		// once more until the sidecar can be written.
		ANS_DBG("Extract",
		        "ExtractProtectedZipFile: sidecar write threw (non-fatal): %s",
		        ex.what());
	}

	return true;
}

