#include "Readers/ItemFilterCatalog.h"

#include <algorithm>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <iostream>

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>

#include <nlohmann/json.hpp>

// -------------------------------------------------------------------------
// Default config JSON written when item_filters.json is absent
// -------------------------------------------------------------------------

static constexpr const char* kDefaultConfigJson = R"({
  "isHouse": {
    "desc": "Structures and storage",
    "Color": "#06b6d4",
    "ModelName": [
      "\\containers\\",
      "\\camping\\",
      "\\cooking\\"
    ]
  },
  "isWeapon": {"desc": "Weapons", "Color": "#f43f5e", "TypeName": "weapon"},
  "isAmmo": {"desc": "Ammo", "Color": "#f59e0b", "ModelName": "\\ammunition\\"},
  "isFood": {"desc": "Food and drinks", "Color": "#22c55e", "ModelName": ["\\food\\", "\\drinks\\"]},
  "isClothing": {"desc": "Clothing", "Color": "#eab308", "TypeName": "clothing"},
  "isBackpack": {"desc": "Backpacks", "Color": "#84cc16", "TypeName": "backpack"},
  "isMedical": {"desc": "Medical supplies", "Color": "#ec4899", "ModelName": "\\medical\\"},
  "isVehiclePart": {"desc": "Vehicle parts", "Color": "#0ea5e9", "TypeName": "carwheel"},
  "isTool": {"desc": "Tools", "Color": "#14b8a6", "ModelName": "\\tools\\"},
  "isCrafting": {"desc": "Crafting materials", "Color": "#c084fc", "ModelName": "\\crafting\\"},
  "isConsumables": {"desc": "Consumables", "Color": "#f97316", "ModelName": "\\consumables\\"},
  "isOptics": {"desc": "Optics", "Color": "#d946ef", "TypeName": "itemoptics"},
  "isMelee": {"desc": "Melee weapons", "Color": "#dc2626", "ModelName": "\\weapons\\melee\\"},
  "isWeaponAttachments": {"desc": "Weapon attachments", "Color": "#fbbf24", "ModelName": "weapons\\attachments\\"},
  "isExplosives": {"desc": "Explosives", "Color": "#fb923c", "ModelName": "\\explosives\\"},
  "isForBuilding": {"desc": "Base building", "Color": "#0891b2", "ModelName": "\\codelock\\"},
  "isOtherLoot": {"desc": "Other loot", "Color": "#64748b"}
})";

// -------------------------------------------------------------------------
// Helpers: resolve exe-relative path
// -------------------------------------------------------------------------

static std::string GetExeDir() {
    char buf[MAX_PATH] = {};
    GetModuleFileNameA(nullptr, buf, MAX_PATH);
    std::filesystem::path p(buf);
    return p.parent_path().string();
}

// -------------------------------------------------------------------------
// Constructors
// -------------------------------------------------------------------------

ItemFilterCatalog::ItemFilterCatalog()
    : m_configPath(GetExeDir() + "/config/item_filters.json")
{
    Load();
}

ItemFilterCatalog::ItemFilterCatalog(const std::string& configPath)
    : m_configPath(configPath)
{
    Load();
}

// -------------------------------------------------------------------------
// Load
// -------------------------------------------------------------------------

void ItemFilterCatalog::Load() {
    namespace fs = std::filesystem;

    // Write default config if the file does not exist.
    if (!fs::exists(m_configPath)) {
        try {
            fs::create_directories(fs::path(m_configPath).parent_path());
        } catch (const std::exception& e) {
            std::cerr << "[ItemFilterCatalog] Could not create config directory: "
                      << e.what() << "\n";
        }
        WriteDefaultConfig();
    }

    // Read and parse the JSON file.
    std::ifstream ifs(m_configPath);
    if (!ifs.is_open()) {
        std::cerr << "[ItemFilterCatalog] Failed to open: " << m_configPath << "\n";
        return;
    }

    nlohmann::json j;
    try {
        ifs >> j;
    } catch (const nlohmann::json::parse_error& e) {
        std::cerr << "[ItemFilterCatalog] JSON parse error in " << m_configPath
                  << ": " << e.what() << "\n";
        return;
    }

    m_filters.clear();

    // JSON object preserves insertion order via nlohmann's ordered_json —
    // but we use the standard json type here, so iterate as-is.
    for (auto& [key, val] : j.items()) {
        ItemFilterDefinition def;
        def.key = key;

        if (val.contains("desc") && val["desc"].is_string()) {
            def.description = val["desc"].get<std::string>();
        }

        if (val.contains("Color") && val["Color"].is_string()) {
            def.color = val["Color"].get<std::string>();
        }

        def.modelNames  = NormalizeAll(ParseStringOrArray(val, "ModelName"));
        def.configNames = NormalizeAll(ParseStringOrArray(val, "ConfigName"));
        def.typeNames   = NormalizeAll(ParseStringOrArray(val, "TypeName"));
        def.names       = NormalizeAll(ParseStringOrArray(val, "Name"));

        m_filters.push_back(std::move(def));
    }
}

// -------------------------------------------------------------------------
// WriteDefaultConfig
// -------------------------------------------------------------------------

void ItemFilterCatalog::WriteDefaultConfig() const {
    std::ofstream ofs(m_configPath);
    if (!ofs.is_open()) {
        std::cerr << "[ItemFilterCatalog] Could not write default config to: "
                  << m_configPath << "\n";
        return;
    }
    ofs << kDefaultConfigJson;
    std::cout << "[ItemFilterCatalog] Wrote default config to: " << m_configPath << "\n";
}

// -------------------------------------------------------------------------
// Classify
// -------------------------------------------------------------------------

std::string ItemFilterCatalog::Classify(const std::string& entityName,
                                         const std::string& typeName,
                                         const std::string& configName,
                                         const std::string& modelName) const
{
    const std::string normEntity = Normalize(entityName);
    const std::string normType   = Normalize(typeName);
    const std::string normConfig = Normalize(configName);
    const std::string normModel  = Normalize(modelName);

    for (const auto& filter : m_filters) {
        // Skip the fallback entry during forward scanning.
        if (filter.key == kFallbackKey) {
            continue;
        }

        if (MatchesAny(filter.modelNames,  normModel)  ||
            MatchesAny(filter.configNames, normConfig) ||
            MatchesAny(filter.typeNames,   normType)   ||
            MatchesAny(filter.names,       normEntity))
        {
            return filter.key;
        }
    }

    return kFallbackKey;
}

// -------------------------------------------------------------------------
// MatchesAny
// -------------------------------------------------------------------------

bool ItemFilterCatalog::MatchesAny(const std::vector<std::string>& patterns,
                                    const std::string& value)
{
    if (patterns.empty() || value.empty()) return false;

    for (const auto& pattern : patterns) {
        if (pattern.empty()) continue;
        // Check if pattern is contained within value (substring match)
        // or equals value exactly.
        if (value.find(pattern) != std::string::npos) {
            return true;
        }
    }
    return false;
}

// -------------------------------------------------------------------------
// ParseStringOrArray
// -------------------------------------------------------------------------

std::vector<std::string> ItemFilterCatalog::ParseStringOrArray(
    const nlohmann::json& j, const std::string& key)
{
    if (!j.contains(key)) return {};

    const auto& val = j[key];

    if (val.is_string()) {
        return { val.get<std::string>() };
    }

    if (val.is_array()) {
        std::vector<std::string> result;
        result.reserve(val.size());
        for (const auto& elem : val) {
            if (elem.is_string()) {
                result.push_back(elem.get<std::string>());
            }
        }
        return result;
    }

    return {};
}

// -------------------------------------------------------------------------
// Normalize
// -------------------------------------------------------------------------

std::string ItemFilterCatalog::Normalize(const std::string& value) {
    // Trim leading/trailing whitespace, then convert to lowercase.
    auto start = value.begin();
    while (start != value.end() && std::isspace(static_cast<unsigned char>(*start))) {
        ++start;
    }
    auto end = value.end();
    while (end != start) {
        --end;
        if (!std::isspace(static_cast<unsigned char>(*end))) {
            ++end;
            break;
        }
    }

    std::string result(start, end);
    std::transform(result.begin(), result.end(), result.begin(),
                   [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
    return result;
}

// -------------------------------------------------------------------------
// NormalizeAll
// -------------------------------------------------------------------------

std::vector<std::string> ItemFilterCatalog::NormalizeAll(
    const std::vector<std::string>& values)
{
    std::vector<std::string> result;
    result.reserve(values.size());
    for (const auto& v : values) {
        result.push_back(Normalize(v));
    }
    return result;
}
