#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <bcrypt.h>
#include <shlobj.h>

#include "security/license_validator.hpp"
#include "security/hwid_generator.hpp"

#include <algorithm>
#include <array>
#include <chrono>
#include <format>
#include <fstream>
#include <memory>
#include <vector>

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

namespace poe2::security {

namespace {

struct BCryptAlgCloser {
    void operator()(BCRYPT_ALG_HANDLE h) const noexcept {
        if (h) BCryptCloseAlgorithmProvider(h, 0);
    }
};

struct BCryptHashCloser {
    void operator()(BCRYPT_HASH_HANDLE h) const noexcept {
        if (h) BCryptDestroyHash(h);
    }
};

using UniqueAlgHandle = std::unique_ptr<void, BCryptAlgCloser>;
using UniqueHashHandle = std::unique_ptr<void, BCryptHashCloser>;

std::string SanitizeCleanHwid(std::string_view hwid) {
    std::string clean;
    clean.reserve(16);
    for (char c : hwid) {
        if (c == '-' || c == ' ' || c == '\t') continue;
        clean += static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
    }
    if (clean.starts_with("PVT")) {
        clean = clean.substr(3);
    }
    if (clean.size() < 16) {
        clean.append(16 - clean.size(), '0');
    }
    return clean.substr(0, 16);
}

inline bool ConstantTimeCompare(std::string_view a, std::string_view b) noexcept {
    if (a.size() != b.size()) return false;
    unsigned char result = 0;
    for (size_t i = 0; i < a.size(); ++i) {
        result |= static_cast<unsigned char>(a[i] ^ b[i]);
    }
    return result == 0;
}

} // namespace

std::string ComputeHmacSha256Hex(std::string_view key, std::string_view data) {
    BCRYPT_ALG_HANDLE rawAlg = nullptr;
    if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&rawAlg, BCRYPT_SHA256_ALGORITHM, nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG))) {
        return "";
    }
    UniqueAlgHandle alg(rawAlg);

    DWORD hashObjSize = 0;
    DWORD cbData = 0;
    if (!BCRYPT_SUCCESS(BCryptGetProperty(alg.get(), BCRYPT_OBJECT_LENGTH, reinterpret_cast<PBYTE>(&hashObjSize), sizeof(DWORD), &cbData, 0))) {
        return "";
    }

    std::vector<BYTE> hashObject(hashObjSize);
    BCRYPT_HASH_HANDLE rawHash = nullptr;
    if (!BCRYPT_SUCCESS(BCryptCreateHash(
            alg.get(),
            &rawHash,
            hashObject.data(),
            hashObjSize,
            reinterpret_cast<PBYTE>(const_cast<char*>(key.data())),
            static_cast<ULONG>(key.size()),
            0))) {
        return "";
    }
    UniqueHashHandle hash(rawHash);

    if (!BCRYPT_SUCCESS(BCryptHashData(
            hash.get(),
            reinterpret_cast<PBYTE>(const_cast<char*>(data.data())),
            static_cast<ULONG>(data.size()),
            0))) {
        return "";
    }

    std::array<BYTE, 32> hashValue = {0};
    if (!BCRYPT_SUCCESS(BCryptFinishHash(hash.get(), hashValue.data(), static_cast<ULONG>(hashValue.size()), 0))) {
        return "";
    }

    std::string hex;
    hex.reserve(64);
    for (BYTE b : hashValue) {
        hex += std::format("{:02X}", b);
    }
    return hex;
}

std::string GenerateProductKey(
    std::string_view targetHwid,
    LicenseTier tier,
    int durationDays,
    std::string_view secretKey
) {
    const std::string_view secret = secretKey.empty() ? DEFAULT_MASTER_SECRET : secretKey;
    const auto now = std::chrono::system_clock::now();
    const auto nowSec = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();

    uint64_t exp = 0;
    char tierChar = 'L';
    if (tier == LicenseTier::LIFETIME) {
        exp = 0;
        tierChar = 'L';
    } else if (tier == LicenseTier::TRIAL) {
        int days = durationDays > 0 ? durationDays : 1;
        exp = static_cast<uint64_t>(nowSec + days * 86400);
        tierChar = 'T';
    } else {
        int days = durationDays > 0 ? durationDays : 30;
        exp = static_cast<uint64_t>(nowSec + days * 86400);
        tierChar = 'S';
    }

    std::string cleanHwid = SanitizeCleanHwid(targetHwid);
    std::string expHex = std::format("{:08X}", exp);
    std::string body = std::format("{}{}{}", tierChar, expHex, cleanHwid); // 1 + 8 + 16 = 25 chars

    std::string fullHmac = ComputeHmacSha256Hex(secret, body);
    std::string sig = fullHmac.substr(0, 10); // 10 chars HMAC signature

    std::string rawKey = body + sig; // 35 chars
    // Format into PVTK-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX (7 chunks of 5)
    std::string formatted = "PVTK-";
    for (size_t i = 0; i < rawKey.size(); i += 5) {
        if (i > 0) formatted += "-";
        formatted += rawKey.substr(i, 5);
    }
    return formatted;
}

LicenseInfo VerifyProductKey(
    std::string_view keyStr,
    std::string_view currentHwid,
    std::string_view secretKey
) {
    LicenseInfo info;
    info.isValid = false;
    info.tier = LicenseTier::INVALID;

    if (keyStr.empty()) {
        info.message = "Chua nhap ma ban quyen (Empty key).";
        return info;
    }

    // Clean key formatting
    std::string cleaned;
    cleaned.reserve(keyStr.size());
    for (char c : keyStr) {
        if (c == '-' || c == ' ' || c == '\t' || c == '\r' || c == '\n') continue;
        cleaned += static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
    }
    if (cleaned.starts_with("PVTK")) {
        cleaned = cleaned.substr(4);
    }

    if (cleaned.size() != 35) {
        info.message = std::format("Dinh dang ma ban quyen khong hop le (Do dai {}/35).", cleaned.size());
        return info;
    }

    std::string body = cleaned.substr(0, 25);
    std::string sig = cleaned.substr(25, 10);

    const std::string_view secret = secretKey.empty() ? DEFAULT_MASTER_SECRET : secretKey;
    std::string expectedHmac = ComputeHmacSha256Hex(secret, body);
    std::string expectedSig = expectedHmac.substr(0, 10);

    if (!ConstantTimeCompare(sig, expectedSig)) {
        info.message = "Chu ky bao mat khong hop le (Signature mismatch).";
        return info;
    }

    char tierChar = body[0];
    std::string expHex = body.substr(1, 8);
    std::string keyHwid = body.substr(9, 16);

    std::string myHwid = currentHwid.empty() ? GetMachineHWID() : std::string(currentHwid);
    std::string myCleanHwid = SanitizeCleanHwid(myHwid);

    // Strict Machine HWID verification (Zero backdoor / Anti-piracy binding)
    if (!ConstantTimeCompare(keyHwid, myCleanHwid)) {
        info.message = std::format("Ma ban quyen khong danh cho may tinh nay (Key HWID: {}, Machine: {}).", keyHwid, myCleanHwid);
        return info;
    }

    uint64_t exp = 0;
    try {
        exp = std::stoull(expHex, nullptr, 16);
    } catch (...) {
        info.message = "Thoi han ma ban quyen bi loi cu phap.";
        return info;
    }

    if (tierChar == 'L') {
        info.tier = LicenseTier::LIFETIME;
    } else if (tierChar == 'T') {
        info.tier = LicenseTier::TRIAL;
    } else if (tierChar == 'S') {
        info.tier = LicenseTier::SUBSCRIPTION;
    } else {
        info.message = "Phan loai ban quyen khong xac dinh.";
        return info;
    }

    const auto now = std::chrono::system_clock::now();
    const auto nowSec = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count());

    info.expiresTimestamp = exp;
    info.hwid = myHwid;
    info.rawKey = std::string(keyStr);

    if (info.tier != LicenseTier::LIFETIME && exp <= nowSec) {
        info.daysLeft = 0;
        info.isValid = false;
        info.message = "Ban quyen da het han su dung (Expired).";
        return info;
    }

    if (info.tier == LicenseTier::LIFETIME) {
        info.daysLeft = 99999;
    } else {
        info.daysLeft = static_cast<int64_t>((exp - nowSec) / 86400);
    }

    info.isValid = true;
    info.message = "Ban quyen hop le va da duoc xac thuc thanh cong!";
    return info;
}

std::filesystem::path GetDefaultLicenseFilePath() {
    wchar_t appData[MAX_PATH] = {0};
    if (SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, 0, appData))) {
        std::filesystem::path p = appData;
        p /= "Poe2VisualTool";
        std::error_code ec;
        std::filesystem::create_directories(p, ec);
        return p / "license.dat";
    }
    return std::filesystem::current_path() / "license.dat";
}

bool SaveLicenseFile(const std::filesystem::path& filePath, std::string_view keyStr) {
    std::error_code ec;
    if (filePath.has_parent_path()) {
        std::filesystem::create_directories(filePath.parent_path(), ec);
    }
    std::ofstream out(filePath, std::ios::trunc);
    if (!out.is_open()) return false;
    out << keyStr << "\n";
    return true;
}

std::string LoadLicenseFile(const std::filesystem::path& filePath) {
    if (!std::filesystem::exists(filePath)) return "";
    std::ifstream in(filePath);
    if (!in.is_open()) return "";
    std::string key;
    in >> key;
    return key;
}

std::string_view GetTierName(LicenseTier tier) {
    switch (tier) {
        case LicenseTier::LIFETIME: return "LIFETIME";
        case LicenseTier::SUBSCRIPTION: return "SUBSCRIPTION";
        case LicenseTier::TRIAL: return "TRIAL";
        case LicenseTier::INVALID:
        default: return "INVALID";
    }
}

} // namespace poe2::security
