#pragma once
#include <windows.h>
#include <winhttp.h>
#include <atomic>
#include <string>
#include <thread>
#include <vector>
#include <cstdio>
#include <algorithm>
#pragma comment(lib, "winhttp.lib")

#define WM_APP_DLPROGRESS (WM_USER + 7)

struct Downloader {
    std::atomic<bool> active{false};
    std::atomic<bool> cancel{false};
    int itemIndex = -1;
    std::thread th;

    void start(HWND notify, int index, std::wstring url, std::wstring dest);
    void requestCancel() { cancel = true; }
    void join() { if (th.joinable()) th.join(); }
};

static void DlThread(HWND notify, int index, std::wstring url, std::wstring dest, Downloader* dl) {
    std::wstring tmp = dest + L".part";
    int result = -1;
    HINTERNET hSes = nullptr, hCon = nullptr, hReq = nullptr;
    FILE* f = nullptr;
    do {
        URL_COMPONENTS uc{}; uc.dwStructSize = sizeof(uc);
        wchar_t host[256] = {0}, path[2048] = {0};
        uc.lpszHostName = host; uc.dwHostNameLength = _countof(host);
        uc.lpszUrlPath  = path; uc.dwUrlPathLength  = _countof(path);
        if (!WinHttpCrackUrl(url.c_str(), 0, 0, &uc)) break;
        hSes = WinHttpOpen(L"win-dictation/1.0", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
                           WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
        if (!hSes) break;
        hCon = WinHttpConnect(hSes, host, uc.nPort, 0);
        if (!hCon) break;
        hReq = WinHttpOpenRequest(hCon, L"GET", path, nullptr, WINHTTP_NO_REFERER,
                                  WINHTTP_DEFAULT_ACCEPT_TYPES,
                                  (uc.nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0);
        if (!hReq) break;
        if (!WinHttpSendRequest(hReq, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
                                WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) break;
        if (!WinHttpReceiveResponse(hReq, nullptr)) break;
        DWORD status = 0, sz = sizeof(status);
        WinHttpQueryHeaders(hReq, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                            WINHTTP_HEADER_NAME_BY_INDEX, &status, &sz, WINHTTP_NO_HEADER_INDEX);
        if (status != 200) break;
        ULONGLONG total = 0;
        {
            wchar_t cl[40]; DWORD cls = sizeof(cl);
            if (WinHttpQueryHeaders(hReq, WINHTTP_QUERY_CONTENT_LENGTH,
                                    WINHTTP_HEADER_NAME_BY_INDEX, cl, &cls, WINHTTP_NO_HEADER_INDEX))
                total = (ULONGLONG)_wtoi64(cl);
        }
        if (_wfopen_s(&f, tmp.c_str(), L"wb") != 0 || !f) break;
        std::vector<char> buf(64 * 1024);
        ULONGLONG got = 0; int lastPct = -1; bool ioOk = true;
        for (;;) {
            if (dl->cancel.load()) { result = -2; ioOk = false; break; }
            DWORD avail = 0;
            if (!WinHttpQueryDataAvailable(hReq, &avail)) { ioOk = false; break; }
            if (avail == 0) break;
            DWORD toRead = std::min<DWORD>(avail, (DWORD)buf.size()), rd = 0;
            if (!WinHttpReadData(hReq, buf.data(), toRead, &rd) || rd == 0) { ioOk = false; break; }
            if (fwrite(buf.data(), 1, rd, f) != rd) { ioOk = false; break; }
            got += rd;
            int pct = total ? (int)(got * 100 / total) : 0;
            if (pct != lastPct) { lastPct = pct; PostMessage(notify, WM_APP_DLPROGRESS, index, pct); }
        }
        fclose(f); f = nullptr;
        if (ioOk && (total == 0 || got == total))
            if (MoveFileExW(tmp.c_str(), dest.c_str(), MOVEFILE_REPLACE_EXISTING))
                result = 101;
    } while (false);
    if (f) fclose(f);
    if (result != 101) DeleteFileW(tmp.c_str());
    if (hReq) WinHttpCloseHandle(hReq);
    if (hCon) WinHttpCloseHandle(hCon);
    if (hSes) WinHttpCloseHandle(hSes);
    PostMessage(notify, WM_APP_DLPROGRESS, index, result);
}

inline void Downloader::start(HWND notify, int index, std::wstring url, std::wstring dest) {
    if (active.exchange(true)) return;
    cancel = false; itemIndex = index;
    if (th.joinable()) th.join();
    th = std::thread(DlThread, notify, index, std::move(url), std::move(dest), this);
}
