#pragma once
#include <Windows.h>
#include <winhttp.h>
#include <string>
#include "strcode.h"
#include "mpstr.h"
#include <chrono>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include "syncutil.h"

// Generated by ChatGTP

using namespace System;
using namespace System::Threading;
using namespace System::Reflection;

std::wstring GetLastErrorString ()
{
	DWORD errCode = GetLastError ();
	if (errCode == 0)
		return L"No error";

	wchar_t* msgBuffer = nullptr;

	// FORMAT_MESSAGE_ALLOCATE_BUFFER: 让系统分配缓冲区
	// FORMAT_MESSAGE_FROM_SYSTEM: 从系统获取错误信息
	// FORMAT_MESSAGE_IGNORE_INSERTS: 忽略 %1 %2
	DWORD size = FormatMessageW (
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		nullptr,
		errCode,
		0, // 默认语言
		(LPWSTR)&msgBuffer,
		0,
		nullptr
	);

	std::wstring msg;
	if (size && msgBuffer)
	{
		msg = msgBuffer;
		LocalFree (msgBuffer); // 释放缓冲区
	}
	else
	{
		msg = L"Unknown error code: " + std::to_wstring (errCode);
	}
	return msg;
}

CriticalSection g_download_cs;

public ref class DownloadHelper
{
	public:
	static void DownloadFile (
		String^ httpUrl,
		String^ savePath,
		Object^ onProgress,
		Object^ onComplete,
		Object^ onError)
	{
		DownloadHelper^ obj = gcnew DownloadHelper ();
		obj->m_url = httpUrl;
		obj->m_savePath = savePath;
		obj->cbProgress = onProgress;
		obj->cbComplete = onComplete;
		obj->cbError = onError;

		Thread^ th = gcnew Thread (gcnew ThreadStart (obj, &DownloadHelper::Worker));
		th->IsBackground = true;
		th->Start ();
	}

	private:
	HINTERNET hSession = nullptr,
		hConnect = nullptr,
		hRequest = nullptr;
	void CancelHttpHandle (HINTERNET hInternet)
	{
		if (hInternet) WinHttpCloseHandle (hInternet);
		hInternet = nullptr;
	}
	std::wstring FormatSpeed (long long speed)
	{
		if (speed < 0) return L"--/s";

		const wchar_t* units [] = {L"B/s", L"KB/s", L"MB/s", L"GB/s", L"TB/s"};
		double s = (double)speed;
		int idx = 0;

		while (s >= 1024.0 && idx < 4) {
			s /= 1024.0;
			if (s / 1024.0 < 1) break;
			idx++;
		}

		wchar_t buf [64];
		swprintf (buf, 64, L"%.2f %s", s, units [idx]);
		return buf;
	}
	public:
	~DownloadHelper ()
	{
		if (hSession) CancelHttpHandle (hSession);
		if (hConnect) CancelHttpHandle (hConnect);
		if (hRequest) CancelHttpHandle (hRequest);
	}
	private:
	void Worker ()
	{
		CreateScopedLock (g_download_cs);
		std::wstring url = MPStringToStdW (m_url);
		std::wstring outPath = MPStringToStdW (m_savePath);

		URL_COMPONENTS urlComp = {0};
		urlComp.dwStructSize = sizeof (urlComp);

		wchar_t host [256];
		wchar_t path [2048];

		urlComp.lpszHostName = host;
		urlComp.dwHostNameLength = _countof (host);

		urlComp.lpszUrlPath = path;
		urlComp.dwUrlPathLength = _countof (path);

		if (!WinHttpCrackUrl (url.c_str (), 0, 0, &urlComp))
		{
			ReportError (outPath, L"WinHttpCrackUrl failed: " + GetLastErrorString ());
			return;
		}

		BOOL isHttps = (urlComp.nScheme == INTERNET_SCHEME_HTTPS);

		hSession = WinHttpOpen (
			L"MyDownloader",
			WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
			WINHTTP_NO_PROXY_NAME,
			WINHTTP_NO_PROXY_BYPASS,
			0);

		if (!hSession)
		{
			ReportError (outPath, L"WinHttpOpen failed: " + GetLastErrorString ());
			return;
		}

		DWORD protocols =
			WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;

		WinHttpSetOption (
			hSession,
			WINHTTP_OPTION_SECURE_PROTOCOLS,
			&protocols,
			sizeof (protocols)
		);

		hConnect = WinHttpConnect (
			hSession,
			urlComp.lpszHostName,
			urlComp.nPort,
			0);

		if (!hConnect)
		{
			CancelHttpHandle (hSession); hSession = nullptr;
			ReportError (outPath, L"WinHttpConnect failed: " + GetLastErrorString ());
			return;
		}

		hRequest = WinHttpOpenRequest (
			hConnect,
			L"GET",
			urlComp.lpszUrlPath,
			NULL,
			WINHTTP_NO_REFERER,
			WINHTTP_DEFAULT_ACCEPT_TYPES,
			isHttps ? WINHTTP_FLAG_SECURE : 0);
		DWORD redirect = WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS;
		WinHttpSetOption (
			hRequest,
			WINHTTP_OPTION_REDIRECT_POLICY,
			&redirect,
			sizeof (redirect)
		);

		if (!hRequest)
		{ 
			CancelHttpHandle (hConnect); hConnect = nullptr;
			CancelHttpHandle (hSession); hSession = nullptr;
			ReportError (outPath, L"WinHttpOpenRequest failed: " + GetLastErrorString ());
			return;
		}
		LPCWSTR ua = L"Mozilla/5.0 (Windows NT 6.2; Win32; x86) AppInstallerUpdater/1.0";
		WinHttpAddRequestHeaders (
			hRequest,
			(std::wstring (L"User-Agent: ") + ua).c_str (),
			-1,
			WINHTTP_ADDREQ_FLAG_ADD
		);

		if (!WinHttpSendRequest (hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
			WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
		{
			CancelHttpHandle (hRequest);
			CancelHttpHandle (hConnect);
			CancelHttpHandle (hSession);
			ReportError (outPath, L"WinHttpSendRequest failed: " + GetLastErrorString ());
			return;
		}

		if (!WinHttpReceiveResponse (hRequest, NULL))
		{
			CancelHttpHandle (hRequest); hRequest = nullptr;
			CancelHttpHandle (hConnect); hConnect = nullptr;
			CancelHttpHandle (hSession); hSession = nullptr;
			DWORD status = 0;
			DWORD size = sizeof (status);

			WinHttpQueryHeaders (
				hRequest,
				WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
				NULL,
				&status,
				&size,
				NULL
			);

			if (status >= 400)
			{
				ReportError (outPath,
					L"HTTP error: " + std::to_wstring (status));
				return;
			}

			ReportError (outPath, L"WinHttpReceiveResponse failed: " + GetLastErrorString ());
			return;
		}

		// ---- 获取 Content-Length ----
		DWORD dwSize = sizeof (DWORD);
		DWORD fileSize = 0;
		WinHttpQueryHeaders (
			hRequest,
			WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER,
			NULL,
			&fileSize,
			&dwSize,
			NULL);

		HANDLE hOut = CreateFileW (outPath.c_str (), GENERIC_WRITE, 0, NULL,
			CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

		if (hOut == INVALID_HANDLE_VALUE)
		{
			CancelHttpHandle (hRequest); hRequest = nullptr;
			CancelHttpHandle (hConnect); hConnect = nullptr;
			CancelHttpHandle (hSession); hSession = nullptr;
			ReportError (outPath, L"Cannot create output file: " + GetLastErrorString ());
			return;
		}

		BYTE buffer [8192];
		DWORD bytesRead = 0;
		DWORD bytesWritten = 0;
		long long received = 0;

		auto t0 = std::chrono::high_resolution_clock::now ();
		auto lastCheck = t0;
		unsigned long long lastBytes = 0;

		while (WinHttpReadData (hRequest, buffer, sizeof (buffer), &bytesRead) && bytesRead > 0)
		{
			WriteFile (hOut, buffer, bytesRead, &bytesWritten, NULL);
			received += bytesRead;

			auto now = std::chrono::high_resolution_clock::now ();
			double intervalSec = std::chrono::duration<double> (now - lastCheck).count ();

			long long speed = -1; // -1 表示“保持上次速度”

								  // 每 0.5 秒刷新一次速度（可调）
			if (intervalSec >= 0.5)
			{
				unsigned long long bytesInInterval = received - lastBytes;
				if (intervalSec > 0)
					speed = (long long)(bytesInInterval / intervalSec);  // B/s

				lastCheck = now;
				lastBytes = received;
			}

			ReportProgress (received, fileSize, speed);
		}


		CloseHandle (hOut);
		CancelHttpHandle (hRequest); hRequest = nullptr;
		CancelHttpHandle (hConnect); hConnect = nullptr;
		CancelHttpHandle (hSession); hSession = nullptr;

		ReportComplete (outPath, received);
	}

	// ---------------- 回调组装 JSON ----------------

	void ReportProgress (long long received, long long total, long long speed)
	{
		if (!cbProgress) return;

		rapidjson::StringBuffer buf;
		rapidjson::Writer<rapidjson::StringBuffer> w (buf);

		w.StartObject ();
		w.Key ("received"); w.Uint64 (received);
		w.Key ("total");    w.Uint64 (total);
		std::wstring speedText = FormatSpeed (speed);
		std::string speedUtf8 = WStringToString (speedText, CP_UTF8);

		w.Key ("speed");
		w.String (speedUtf8.c_str ());

		w.Key ("progress"); w.Double (received / (double)total * 100);
		w.EndObject ();

		CallJS (cbProgress, CStringToMPString (StringToWString (buf.GetString (), CP_UTF8)));
	}

	void ReportComplete (const std::wstring& file, long long size)
	{
		if (!cbComplete) return;

		rapidjson::StringBuffer buf;
		rapidjson::Writer<rapidjson::StringBuffer> w (buf);

		w.StartObject ();
		w.Key ("file");   w.String (WStringToString (file, CP_UTF8).c_str ());
		w.Key ("status"); w.String ("ok");
		w.Key ("size");   w.Uint64 (size);
		w.EndObject ();

		CallJS (cbComplete, CStringToMPString (StringToWString (buf.GetString (), CP_UTF8)));
	}

	void ReportError (const std::wstring& file, const std::wstring &reason)
	{
		if (!cbError) return;

		rapidjson::StringBuffer buf;
		rapidjson::Writer<rapidjson::StringBuffer> w (buf);

		w.StartObject ();
		w.Key ("file");   w.String (WStringToString (file, CP_UTF8).c_str ());
		w.Key ("status"); w.String ("failed");
		w.Key ("reason"); w.String (WStringToString (reason, CP_UTF8).c_str ());
		w.EndObject ();

		CallJS (cbError, CStringToMPString (StringToWString (buf.GetString (), CP_UTF8)));
	}

	// ---------------- 调用 JS 回调 ----------------
	void CallJS (Object^ jsFunc, String^ arg)
	{
		if (!jsFunc) return;
		try
		{
			jsFunc->GetType ()->InvokeMember (
				"call",
				BindingFlags::InvokeMethod,
				nullptr,
				jsFunc,
				gcnew array<Object^>{ 1, arg }
			);
		}
		catch (...)
		{
			// 失败可忽略
		}
	}

	private:
	String^ m_url;
	String^ m_savePath;

	Object^ cbProgress;
	Object^ cbComplete;
	Object^ cbError;
};

using namespace System::Runtime::InteropServices;
[ComVisible (true)] 
public ref class _I_Download 
{ 
	public:
	void WorkAsync (String ^httpurl, String ^saveFilePath, Object ^onComplete, Object ^onError, Object ^onProgress) 
	{ 
		auto download = gcnew DownloadHelper ();
		download->DownloadFile (httpurl, saveFilePath, onProgress, onComplete, onError);
	} 
};