// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#include "lite_rollout_policy.h"

#include <nlohmann/json.hpp>

#include <algorithm>
#include <cstdint>
#include <fstream>
#include <sstream>

namespace dragonx {
namespace wallet {

namespace {
using json = nlohmann::json;

// Numeric dotted core of a version, dropping any "-prerelease" / "+build" suffix.
std::vector<long long> parseVersionCore(const std::string& v)
{
    const std::string core = v.substr(0, v.find_first_of("-+"));
    std::vector<long long> parts;
    std::stringstream ss(core);
    std::string token;
    while (std::getline(ss, token, '.')) {
        if (token.empty()) { parts.push_back(0); continue; }
        try { parts.push_back(std::stoll(token)); }
        catch (...) { parts.push_back(0); }
    }
    return parts;
}

const char* defaultMessageFor(LiteRolloutStatus s)
{
    switch (s) {
    case LiteRolloutStatus::DisabledByKillSwitchEnv:
        return "The lite wallet is disabled by an emergency kill-switch on this machine.";
    case LiteRolloutStatus::DisabledByLocalOverride:
        return "The lite wallet is turned off in this app's settings.";
    case LiteRolloutStatus::DisabledByManifest:
        return "The lite wallet is currently disabled by the rollout policy.";
    case LiteRolloutStatus::DisabledUnsupportedVersion:
        return "This app version is not supported by the lite wallet; please update.";
    case LiteRolloutStatus::DisabledBlockedVersion:
        return "This app version has a known issue and the lite wallet is disabled; please update.";
    case LiteRolloutStatus::DisabledByStagedRollout:
        return "The lite wallet is rolling out gradually and is not yet enabled for this install.";
    case LiteRolloutStatus::Allowed:
    default:
        return "";
    }
}

LiteRolloutDecision disabledDecision(LiteRolloutStatus s, const std::string& manifestMessage)
{
    LiteRolloutDecision d;
    d.allowed = false;
    d.status = s;
    d.message = !manifestMessage.empty() ? manifestMessage : defaultMessageFor(s);
    return d;
}
} // namespace

int compareLiteVersions(const std::string& a, const std::string& b)
{
    const auto pa = parseVersionCore(a);
    const auto pb = parseVersionCore(b);
    const std::size_t n = std::max(pa.size(), pb.size());
    for (std::size_t i = 0; i < n; ++i) {
        const long long va = i < pa.size() ? pa[i] : 0;
        const long long vb = i < pb.size() ? pb[i] : 0;
        if (va < vb) return -1;
        if (va > vb) return 1;
    }
    return 0;
}

int liteRolloutBucketFromInstallId(const std::string& installId)
{
    if (installId.empty()) return 0;
    // FNV-1a (64-bit) → 0..999. Stable across runs for a given id.
    std::uint64_t h = 1469598103934665603ULL;
    for (unsigned char c : installId) {
        h ^= static_cast<std::uint64_t>(c);
        h *= 1099511628211ULL;
    }
    return static_cast<int>(h % 1000ULL);
}

LiteRolloutOverride liteRolloutOverrideFromString(const std::string& value)
{
    if (value == "force_on")  return LiteRolloutOverride::ForceOn;
    if (value == "force_off") return LiteRolloutOverride::ForceOff;
    return LiteRolloutOverride::Auto;
}

const char* liteRolloutOverrideToString(LiteRolloutOverride o)
{
    switch (o) {
    case LiteRolloutOverride::ForceOn:  return "force_on";
    case LiteRolloutOverride::ForceOff: return "force_off";
    case LiteRolloutOverride::Auto:     default: return "auto";
    }
}

const char* liteRolloutStatusName(LiteRolloutStatus s)
{
    switch (s) {
    case LiteRolloutStatus::Allowed:                    return "Allowed";
    case LiteRolloutStatus::DisabledByKillSwitchEnv:    return "DisabledByKillSwitchEnv";
    case LiteRolloutStatus::DisabledByLocalOverride:    return "DisabledByLocalOverride";
    case LiteRolloutStatus::DisabledByManifest:         return "DisabledByManifest";
    case LiteRolloutStatus::DisabledUnsupportedVersion: return "DisabledUnsupportedVersion";
    case LiteRolloutStatus::DisabledBlockedVersion:     return "DisabledBlockedVersion";
    case LiteRolloutStatus::DisabledByStagedRollout:    return "DisabledByStagedRollout";
    }
    return "Unknown";
}

LiteRolloutDecision evaluateLiteRollout(const LiteRolloutInputs& in)
{
    // 1. Emergency env kill-switch is absolute — not even ForceOn bypasses it.
    if (in.killSwitchEnv)
        return disabledDecision(LiteRolloutStatus::DisabledByKillSwitchEnv, {});

    // 2/3. Local override.
    if (in.override == LiteRolloutOverride::ForceOff)
        return disabledDecision(LiteRolloutStatus::DisabledByLocalOverride, {});
    if (in.override == LiteRolloutOverride::ForceOn)
        return LiteRolloutDecision{true, LiteRolloutStatus::Allowed, {}};

    // 4. Manifest gates — fail-open: only enforced when a manifest is present AND valid.
    const LiteRolloutManifest& m = in.manifest;
    if (m.present && m.valid) {
        if (!m.globalEnabled)
            return disabledDecision(LiteRolloutStatus::DisabledByManifest, m.message);
        if (!m.minVersion.empty() && compareLiteVersions(in.appVersion, m.minVersion) < 0)
            return disabledDecision(LiteRolloutStatus::DisabledUnsupportedVersion, m.message);
        if (!m.maxVersion.empty() && compareLiteVersions(in.appVersion, m.maxVersion) > 0)
            return disabledDecision(LiteRolloutStatus::DisabledUnsupportedVersion, m.message);
        for (const auto& bad : m.blockedVersions)
            if (compareLiteVersions(in.appVersion, bad) == 0)
                return disabledDecision(LiteRolloutStatus::DisabledBlockedVersion, m.message);
        if (in.installBucket >= m.rolloutPermille)
            return disabledDecision(LiteRolloutStatus::DisabledByStagedRollout, m.message);
    }

    // 5. Fail-open default.
    return LiteRolloutDecision{true, LiteRolloutStatus::Allowed, {}};
}

LiteRolloutManifest loadLiteRolloutManifestFromFile(const std::string& path)
{
    LiteRolloutManifest m;  // present=false, valid=false (fail-open)
    std::ifstream in(path);
    if (!in.is_open()) return m;  // no file -> no manifest
    m.present = true;
    try {
        json j;
        in >> j;
        if (j.contains("global_enabled")) m.globalEnabled = j["global_enabled"].get<bool>();
        if (j.contains("min_version"))    m.minVersion    = j["min_version"].get<std::string>();
        if (j.contains("max_version"))    m.maxVersion    = j["max_version"].get<std::string>();
        if (j.contains("blocked_versions"))
            m.blockedVersions = j["blocked_versions"].get<std::vector<std::string>>();
        if (j.contains("rollout_permille")) {
            const int p = j["rollout_permille"].get<int>();
            m.rolloutPermille = std::max(0, std::min(1000, p));
        }
        if (j.contains("message")) m.message = j["message"].get<std::string>();
        m.valid = true;
    } catch (...) {
        m.valid = false;  // parse error -> fail-open
    }
    return m;
}

} // namespace wallet
} // namespace dragonx
