#include "Hash.h"
#include <windows.h>
#include <wincrypt.h>
#include <cstdio>

#pragma comment(lib, "advapi32.lib")

// ─── 内部辅助：字节数组 → 十六进制字符串 ─────────────────────
static std::string ToHexString(const BYTE* data, DWORD length)
{
	std::string result;
	result.reserve(length * 2);
	for (DWORD i = 0; i < length; ++i)
	{
		char buf[3];
		snprintf(buf, sizeof(buf), "%02x", data[i]);
		result.append(buf);
	}
	return result;
}

// ─── 内部辅助：获取 MD5 的 CryptoAPI 提供者 ─────────────────
static bool AcquireCryptoProvider(HCRYPTPROV& hProv)
{
	if (!CryptAcquireContextA(&hProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
	{
		printf("[Hash] > err: CryptAcquireContext failed (0x%lx)\n", GetLastError());
		return false;
	}
	return true;
}

// ─── 内部：执行 MD5 哈希 ──────────────────────────────────────
static bool DoMD5Hash(HCRYPTPROV hProv, HCRYPTHASH& hHash, const BYTE* data, DWORD dataLen)
{
	if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))
	{
		printf("[Hash] > err: CryptCreateHash failed (0x%lx)\n", GetLastError());
		return false;
	}

	if (!CryptHashData(hHash, data, dataLen, 0))
	{
		printf("[Hash] > err: CryptHashData failed (0x%lx)\n", GetLastError());
		CryptDestroyHash(hHash);
		return false;
	}
	return true;
}

// ─── 公开接口 ─────────────────────────────────────────────────

int64_t Hash::GetFileSize(const std::string& filePath)
{
	HANDLE hFile = CreateFileA(
		filePath.c_str(),
		GENERIC_READ,
		FILE_SHARE_READ,
		nullptr,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		nullptr
	);

	if (hFile == INVALID_HANDLE_VALUE)
		return -1;

	LARGE_INTEGER size;
	const bool ok = GetFileSizeEx(hFile, &size);
	CloseHandle(hFile);

	return ok ? static_cast<int64_t>(size.QuadPart) : -1;
}

std::string Hash::ComputeMD5(const std::string& filePath)
{
	// 打开文件
	HANDLE hFile = CreateFileA(
		filePath.c_str(),
		GENERIC_READ,
		FILE_SHARE_READ,
		nullptr,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		nullptr
	);

	if (hFile == INVALID_HANDLE_VALUE)
	{
		printf("[Hash] > err: cannot open file: %s\n", filePath.c_str());
		return {};
	}

	// 获取文件大小
	LARGE_INTEGER fileSize;
	if (!GetFileSizeEx(hFile, &fileSize))
	{
		printf("[Hash] > err: GetFileSizeEx failed\n");
		CloseHandle(hFile);
		return {};
	}

	// 映射文件到内存
	HANDLE hMapping = CreateFileMappingA(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
	if (!hMapping)
	{
		printf("[Hash] > err: CreateFileMapping failed (0x%lx)\n", GetLastError());
		CloseHandle(hFile);
		return {};
	}

	const BYTE* pData = static_cast<const BYTE*>(
		MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0)
	);

	if (!pData)
	{
		printf("[Hash] > err: MapViewOfFile failed (0x%lx)\n", GetLastError());
		CloseHandle(hMapping);
		CloseHandle(hFile);
		return {};
	}

	// CryptoAPI MD5
	HCRYPTPROV hProv = 0;
	HCRYPTHASH hHash = 0;
	std::string result;

	if (AcquireCryptoProvider(hProv))
	{
		if (DoMD5Hash(hProv, hHash, pData, static_cast<DWORD>(fileSize.QuadPart)))
		{
			BYTE hash[16];
			DWORD hashLen = sizeof(hash);
			if (CryptGetHashParam(hHash, HP_HASHVAL, hash, &hashLen, 0))
			{
				result = ToHexString(hash, hashLen);
			}
			else
			{
				printf("[Hash] > err: CryptGetHashParam failed (0x%lx)\n", GetLastError());
			}
			CryptDestroyHash(hHash);
		}
		CryptReleaseContext(hProv, 0);
	}

	UnmapViewOfFile(pData);
	CloseHandle(hMapping);
	CloseHandle(hFile);

	if (!result.empty())
		printf("[Hash] > %s -> %s\n", filePath.c_str(), result.c_str());

	return result;
}

std::string Hash::ComputeMD5(const std::vector<uint8_t>& data)
{
	if (data.empty())
		return {};

	HCRYPTPROV hProv = 0;
	HCRYPTHASH hHash = 0;
	std::string result;

	if (AcquireCryptoProvider(hProv))
	{
		if (DoMD5Hash(hProv, hHash, data.data(), static_cast<DWORD>(data.size())))
		{
			BYTE hash[16];
			DWORD hashLen = sizeof(hash);
			if (CryptGetHashParam(hHash, HP_HASHVAL, hash, &hashLen, 0))
			{
				result = ToHexString(hash, hashLen);
			}
			CryptDestroyHash(hHash);
		}
		CryptReleaseContext(hProv, 0);
	}

	return result;
}

std::string Hash::ComputeMD5String(const std::string& str)
{
	if (str.empty())
		return {};

	HCRYPTPROV hProv = 0;
	HCRYPTHASH hHash = 0;
	std::string result;

	if (AcquireCryptoProvider(hProv))
	{
		if (DoMD5Hash(hProv, hHash, reinterpret_cast<const BYTE*>(str.data()), static_cast<DWORD>(str.size())))
		{
			BYTE hash[16];
			DWORD hashLen = sizeof(hash);
			if (CryptGetHashParam(hHash, HP_HASHVAL, hash, &hashLen, 0))
			{
				result = ToHexString(hash, hashLen);
			}
			CryptDestroyHash(hHash);
		}
		CryptReleaseContext(hProv, 0);
	}

	return result;
}

bool Hash::CompareFiles(const std::string& path1, const std::string& path2)
{
	// 快速路径：先比较大小
	const int64_t size1 = GetFileSize(path1);
	const int64_t size2 = GetFileSize(path2);

	if (size1 < 0 || size2 < 0)
	{
		printf("[Hash] > err: cannot stat one or both files for comparison\n");
		return false;
	}

	if (size1 != size2)
	{
		printf("[Hash] > files differ: size mismatch (%lld vs %lld)\n", size1, size2);
		return false;
	}

	// 大小相同，比较 MD5
	const std::string hash1 = ComputeMD5(path1);
	const std::string hash2 = ComputeMD5(path2);

	if (hash1.empty() || hash2.empty())
	{
		printf("[Hash] > err: hash computation failed during comparison\n");
		return false;
	}

	const bool same = (hash1 == hash2);
	printf("[Hash] > compare: %s (%s vs %s)\n", same ? "MATCH" : "DIFFER", hash1.c_str(), hash2.c_str());
	return same;
}

std::string Hash::ComputeSHA256String(const std::string& str)
{
	if (str.empty())
		return {};

	HCRYPTPROV hProv = 0;
	HCRYPTHASH hHash = 0;
	std::string result;

	if (!CryptAcquireContextA(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
	{
		printf("[Hash] SHA256: CryptAcquireContext failed (0x%lx)\n", GetLastError());
		return {};
	}

	if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash))
	{
		printf("[Hash] SHA256: CryptCreateHash failed (0x%lx)\n", GetLastError());
		CryptReleaseContext(hProv, 0);
		return {};
	}

	if (!CryptHashData(hHash, reinterpret_cast<const BYTE*>(str.data()), static_cast<DWORD>(str.size()), 0))
	{
		printf("[Hash] SHA256: CryptHashData failed (0x%lx)\n", GetLastError());
		CryptDestroyHash(hHash);
		CryptReleaseContext(hProv, 0);
		return {};
	}

	BYTE hash[32];
	DWORD hashLen = sizeof(hash);
	if (CryptGetHashParam(hHash, HP_HASHVAL, hash, &hashLen, 0))
	{
		char hex[65];
		for (int i = 0; i < 32; ++i)
			snprintf(hex + i * 2, 3, "%02x", hash[i]);
		result.assign(hex, 64);
	}
	else
	{
		printf("[Hash] SHA256: CryptGetHashParam failed (0x%lx)\n", GetLastError());
	}

	CryptDestroyHash(hHash);
	CryptReleaseContext(hProv, 0);
	return result;
}

bool Hash::CompareFileWithMemory(const std::string& filePath, const std::vector<uint8_t>& memData)
{
	// 快速路径：先比较大小
	const int64_t fileSize = GetFileSize(filePath);

	if (fileSize < 0)
	{
		printf("[Hash] > err: cannot stat file for memory comparison: %s\n", filePath.c_str());
		return false;
	}

	if (static_cast<size_t>(fileSize) != memData.size())
	{
		printf("[Hash] > file vs memory: size mismatch (%lld vs %zu)\n", fileSize, memData.size());
		return false;
	}

	// 大小相同，比较 MD5
	const std::string fileHash = ComputeMD5(filePath);
	const std::string memHash  = ComputeMD5(memData);

	if (fileHash.empty() || memHash.empty())
	{
		printf("[Hash] > err: hash computation failed during file/memory comparison\n");
		return false;
	}

	const bool same = (fileHash == memHash);
	printf("[Hash] > file vs memory: %s (%s vs %s)\n", same ? "MATCH" : "DIFFER", fileHash.c_str(), memHash.c_str());
	return same;
}
