#include "cache-cleaner.h"

#include "win-platform.h"

#include <filesystem>
#include <unordered_set>

#include <shlobj.h>
#include <tlhelp32.h>

#include <seallib/assert.h>

CacheCleaner::CacheCleaner() : _running(false), _monitorThread(nullptr)
{
    _log = new seallib::Logger("Cache cleaner");
}

CacheCleaner::~CacheCleaner()
{
    shutdown();
    delete _log;
}

void CacheCleaner::init()
{
    std::string localAppData = getLocalAppDataPath();
    if (localAppData.empty())
    {
        _log->error("Failed to get LocalAppData");
        return;
    }

    _cacheDir = localAppData + "\\DeadByDaylight\\Saved\\PersistentDownloadDir";

    // this is pointless but just in case
    std::filesystem::create_directories(_cacheDir);

    clearFolder(_cacheDir);

    _running = true;

    _monitorThread = std::make_unique<std::thread>(&CacheCleaner::monitorLoop, this);
}

void CacheCleaner::shutdown()
{
    _log->verbose("shutting down");
    if (!_running) return;
    _running = false;

    if (_monitorThread && _monitorThread->joinable()) _monitorThread->join();
    _monitorThread.reset();
}

std::string CacheCleaner::getLocalAppDataPath()
{
    char buffer[MAX_PATH];
    if (SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, buffer) == S_OK)
    {
        return std::string(buffer);
    }
    return "";
}

void CacheCleaner::clearFolder(std::string& path)
{
    try
    {
        for (const auto& entry : std::filesystem::directory_iterator(path))
        {
            std::filesystem::remove_all(entry.path());
        }
    }
    catch (...)
    {
    }
}

void CacheCleaner::monitorLoop()
{
    HANDLE changeHandle = FindFirstChangeNotificationA(_cacheDir.c_str(), TRUE,
                                                       FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
                                                           FILE_NOTIFY_CHANGE_LAST_WRITE);

    if (changeHandle == INVALID_HANDLE_VALUE)
    {
        _log->error("Failed to create change notification");
        _running = false;
        return;
    }

    _log->verbose("Cache cleaner monitoring started");

    std::unordered_set<DWORD> previousPids;

    while (_running)
    {
        for (int i = 0; i < 20 && _running; i++)
            Sleep(100);

        if (!_running) break;

        HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (hSnapshot == INVALID_HANDLE_VALUE) continue;

        PROCESSENTRY32 pe32;
        pe32.dwSize = sizeof(PROCESSENTRY32);

        std::unordered_set<DWORD> currentPids;

        if (Process32First(hSnapshot, &pe32))
        {
            do
            {
                DWORD pid = pe32.th32ProcessID;
                currentPids.insert(pid);

                if (previousPids.find(pid) == previousPids.end())
                {
                    std::string processName(pe32.szExeFile);

                    for (char& c : processName)
                        c = static_cast<char>(tolower(c));

                    if (processName.find("deadbydaylight") != std::string::npos)
                    {
                        _log->verbose("New DeadByDaylight process detected (PID: {}), clearing cache", pid);
                        clearFolder(_cacheDir);
                    }
                }
            } while (Process32Next(hSnapshot, &pe32));
        }

        CloseHandle(hSnapshot);
        previousPids = std::move(currentPids);
    }

    FindCloseChangeNotification(changeHandle);
}
