#include "wallet/lite_result_parsers.h"

#include <algorithm>
#include <limits>
#include <stdexcept>

namespace dragonx::wallet {
namespace {

using json = nlohmann::json;

std::string fieldPath(const std::string& key)
{
    return "$.'" + key + "'";
}

std::string indexPath(const std::string& parentPath, std::size_t index)
{
    return parentPath + "[" + std::to_string(index) + "]";
}

template <typename Result>
Result makeResult(LiteResultCommand command)
{
    Result result;
    result.command = command;
    return result;
}

template <typename Result>
bool fail(Result& result,
          LiteResultParserError error,
          const std::string& path,
          const std::string& message)
{
    result.ok = false;
    result.error = error;
    result.errorPath = path;
    result.errorMessage = message;
    result.issues.push_back({path, message});
    return false;
}

template <typename Result>
void succeed(Result& result)
{
    result.ok = true;
    result.error = LiteResultParserError::None;
    result.errorPath.clear();
    result.errorMessage.clear();
}

template <typename Result>
bool parseJsonText(const std::string& jsonText, Result& result, json& parsed)
{
    parsed = json::parse(jsonText, nullptr, false);
    if (parsed.is_discarded()) {
        return fail(result, LiteResultParserError::InvalidJson, "$", "response is not valid JSON");
    }
    return true;
}

template <typename Result>
bool requireObject(const json& value, const std::string& path, Result& result)
{
    if (!value.is_object()) {
        return fail(result, LiteResultParserError::ExpectedObject, path, "expected a JSON object");
    }
    return true;
}

template <typename Result>
bool requireArray(const json& value, const std::string& path, Result& result)
{
    if (!value.is_array()) {
        return fail(result, LiteResultParserError::ExpectedArray, path, "expected a JSON array");
    }
    return true;
}

bool jsonToUnsigned(const json& value, std::uint64_t& output)
{
    try {
        if (value.is_number_unsigned()) {
            output = value.get<std::uint64_t>();
            return true;
        }
        if (value.is_number_integer()) {
            const auto signedValue = value.get<std::int64_t>();
            if (signedValue < 0) return false;
            output = static_cast<std::uint64_t>(signedValue);
            return true;
        }
        if (value.is_string()) {
            const auto text = value.get<std::string>();
            if (text.empty()) return false;
            std::size_t parsedSize = 0;
            const auto parsed = std::stoull(text, &parsedSize, 10);
            if (parsedSize != text.size()) return false;
            output = static_cast<std::uint64_t>(parsed);
            return true;
        }
    } catch (const std::exception&) {
        return false;
    }
    return false;
}

bool jsonToSigned(const json& value, std::int64_t& output)
{
    try {
        if (value.is_number_integer()) {
            output = value.get<std::int64_t>();
            return true;
        }
        if (value.is_number_unsigned()) {
            const auto unsignedValue = value.get<std::uint64_t>();
            if (unsignedValue > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) {
                return false;
            }
            output = static_cast<std::int64_t>(unsignedValue);
            return true;
        }
        if (value.is_string()) {
            const auto text = value.get<std::string>();
            if (text.empty()) return false;
            std::size_t parsedSize = 0;
            const auto parsed = std::stoll(text, &parsedSize, 10);
            if (parsedSize != text.size()) return false;
            output = static_cast<std::int64_t>(parsed);
            return true;
        }
    } catch (const std::exception&) {
        return false;
    }
    return false;
}

template <typename Result>
bool readRequiredStringField(const json& object,
                             const std::string& key,
                             std::string& output,
                             Result& result)
{
    if (!object.contains(key) || object.at(key).is_null()) {
        return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required string field");
    }
    if (!object.at(key).is_string()) {
        return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a string");
    }
    output = object.at(key).get<std::string>();
    return true;
}

template <typename Result>
bool readOptionalStringField(const json& object,
                             const std::string& key,
                             std::optional<std::string>& output,
                             Result& result)
{
    if (!object.contains(key) || object.at(key).is_null()) return true;
    if (!object.at(key).is_string()) {
        return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a string");
    }
    output = object.at(key).get<std::string>();
    return true;
}

template <typename Result>
bool readOptionalStringField(const json& object,
                             const std::string& key,
                             std::string& output,
                             Result& result)
{
    if (!object.contains(key) || object.at(key).is_null()) return true;
    if (!object.at(key).is_string()) {
        return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a string");
    }
    output = object.at(key).get<std::string>();
    return true;
}

template <typename Result>
bool readRequiredSignedField(const json& object,
                             const std::string& key,
                             std::int64_t& output,
                             Result& result)
{
    if (!object.contains(key) || object.at(key).is_null()) {
        return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required integer field");
    }
    if (!jsonToSigned(object.at(key), output)) {
        return fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "expected a signed integer-compatible value");
    }
    return true;
}

template <typename Result>
bool readRequiredUnsignedField(const json& object,
                               const std::string& key,
                               std::uint64_t& output,
                               Result& result)
{
    if (!object.contains(key) || object.at(key).is_null()) {
        return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required unsigned integer field");
    }
    if (!jsonToUnsigned(object.at(key), output)) {
        return fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "expected an unsigned integer-compatible value");
    }
    return true;
}

template <typename Result>
bool readOptionalSignedField(const json& object,
                             const std::string& key,
                             std::optional<std::int64_t>& output,
                             Result& result)
{
    if (!object.contains(key) || object.at(key).is_null()) return true;
    std::int64_t parsed = 0;
    if (!jsonToSigned(object.at(key), parsed)) {
        return fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "expected a signed integer-compatible value");
    }
    output = parsed;
    return true;
}

template <typename Result>
bool readOptionalBoolField(const json& object,
                           const std::string& key,
                           bool& output,
                           Result& result)
{
    if (!object.contains(key) || object.at(key).is_null()) return true;
    if (!object.at(key).is_boolean()) {
        return fail(result, LiteResultParserError::InvalidFieldType, fieldPath(key), "expected a boolean");
    }
    output = object.at(key).get<bool>();
    return true;
}

template <typename Result>
bool readStringArrayField(const json& object,
                          const std::string& key,
                          std::vector<std::string>& output,
                          Result& result)
{
    if (!object.contains(key) || object.at(key).is_null()) {
        return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required array field");
    }
    const auto& values = object.at(key);
    if (!requireArray(values, fieldPath(key), result)) return false;

    output.clear();
    for (std::size_t index = 0; index < values.size(); ++index) {
        if (!values[index].is_string()) {
            return fail(result, LiteResultParserError::InvalidFieldType, indexPath(fieldPath(key), index), "expected a string array item");
        }
        output.push_back(values[index].get<std::string>());
    }
    return true;
}

template <typename Result>
bool parseSpendableOutput(const json& value,
                          LiteSpendableOutputKind kind,
                          const std::string& path,
                          LiteSpendableOutput& output,
                          Result& result)
{
    if (!requireObject(value, path, result)) return false;

    output.kind = kind;
    output.pending = kind == LiteSpendableOutputKind::PendingNote || kind == LiteSpendableOutputKind::PendingUtxo;
    if (!readRequiredStringField(value, "address", output.address, result)) return false;
    if (!readRequiredStringField(value, "created_in_txid", output.createdInTxid, result)) return false;
    if (!readOptionalSignedField(value, "created_in_block", output.createdInBlock, result)) return false;
    if (!readRequiredUnsignedField(value, "value", output.value, result)) return false;
    if (!readOptionalBoolField(value, "spent", output.spent, result)) return false;
    if (!readOptionalBoolField(value, "unconfirmed_spent", output.unconfirmedSpent, result)) return false;
    output.spendable = !output.pending && !output.spent && !output.unconfirmedSpent;
    return true;
}

template <typename Result>
bool parseSpendableOutputArray(const json& object,
                               const std::string& key,
                               LiteSpendableOutputKind kind,
                               std::vector<LiteSpendableOutput>& output,
                               Result& result)
{
    if (!object.contains(key) || object.at(key).is_null()) {
        return fail(result, LiteResultParserError::MissingField, fieldPath(key), "missing required spendable output array");
    }
    const auto& values = object.at(key);
    if (!requireArray(values, fieldPath(key), result)) return false;

    output.clear();
    for (std::size_t index = 0; index < values.size(); ++index) {
        LiteSpendableOutput parsed;
        if (!parseSpendableOutput(values[index], kind, indexPath(fieldPath(key), index), parsed, result)) return false;
        output.push_back(std::move(parsed));
    }
    return true;
}

template <typename Result>
bool parseTransactionOutput(const json& value,
                            const std::string& path,
                            LiteTransactionOutput& output,
                            Result& result)
{
    if (!requireObject(value, path, result)) return false;
    if (!readRequiredStringField(value, "address", output.address, result)) return false;
    if (!readRequiredSignedField(value, "value", output.value, result)) return false;
    if (!readOptionalStringField(value, "memo", output.memo, result)) return false;
    return true;
}

template <typename Result>
bool parseTransactionRecord(const json& value,
                            const std::string& path,
                            LiteTransactionRecord& output,
                            Result& result)
{
    if (!requireObject(value, path, result)) return false;
    if (!readRequiredStringField(value, "txid", output.txid, result)) return false;
    if (!readRequiredSignedField(value, "datetime", output.datetime, result)) return false;
    if (!readOptionalSignedField(value, "block_height", output.blockHeight, result)) return false;
    if (!readOptionalBoolField(value, "unconfirmed", output.unconfirmed, result)) return false;

    if (value.contains("outgoing_metadata") && !value.at("outgoing_metadata").is_null()) {
        const auto& metadata = value.at("outgoing_metadata");
        if (!requireArray(metadata, path + ".outgoing_metadata", result)) return false;
        output.direction = LiteTransactionDirection::Send;
        for (std::size_t index = 0; index < metadata.size(); ++index) {
            LiteTransactionOutput parsedOutput;
            if (!parseTransactionOutput(metadata[index], path + ".outgoing_metadata[" + std::to_string(index) + "]", parsedOutput, result)) return false;
            output.amount += parsedOutput.value;
            output.outgoingMetadata.push_back(std::move(parsedOutput));
        }
        return true;
    }

    output.direction = LiteTransactionDirection::Receive;
    if (!readRequiredStringField(value, "address", output.address, result)) return false;
    if (!readRequiredSignedField(value, "amount", output.amount, result)) return false;
    if (!readOptionalStringField(value, "memo", output.memo, result)) return false;
    if (!readOptionalSignedField(value, "position", output.position, result)) return false;
    return true;
}

} // namespace

const char* liteResultCommandName(LiteResultCommand command)
{
    switch (command) {
        case LiteResultCommand::Info: return "info";
        case LiteResultCommand::Height: return "height";
        case LiteResultCommand::Balance: return "balance";
        case LiteResultCommand::Addresses: return "addresses";
        case LiteResultCommand::List: return "list";
        case LiteResultCommand::Notes: return "notes";
        case LiteResultCommand::SyncStatus: return "syncstatus";
    }
    return "unknown";
}

const char* liteResultParserErrorName(LiteResultParserError error)
{
    switch (error) {
        case LiteResultParserError::None: return "none";
        case LiteResultParserError::InvalidJson: return "invalid_json";
        case LiteResultParserError::ExpectedObject: return "expected_object";
        case LiteResultParserError::ExpectedArray: return "expected_array";
        case LiteResultParserError::MissingField: return "missing_field";
        case LiteResultParserError::InvalidFieldType: return "invalid_field_type";
        case LiteResultParserError::InvalidFieldValue: return "invalid_field_value";
    }
    return "unknown";
}

const char* liteSpendableOutputKindName(LiteSpendableOutputKind kind)
{
    switch (kind) {
        case LiteSpendableOutputKind::UnspentNote: return "unspent_note";
        case LiteSpendableOutputKind::Utxo: return "utxo";
        case LiteSpendableOutputKind::PendingNote: return "pending_note";
        case LiteSpendableOutputKind::PendingUtxo: return "pending_utxo";
    }
    return "unknown";
}

const char* liteTransactionDirectionName(LiteTransactionDirection direction)
{
    switch (direction) {
        case LiteTransactionDirection::Unknown: return "unknown";
        case LiteTransactionDirection::Send: return "send";
        case LiteTransactionDirection::Receive: return "receive";
    }
    return "unknown";
}

LiteInfoParseResult parseLiteInfoResponse(const std::string& jsonText)
{
    auto result = makeResult<LiteInfoParseResult>(LiteResultCommand::Info);
    json parsed;
    if (!parseJsonText(jsonText, result, parsed)) return result;
    return parseLiteInfoResponse(parsed);
}

LiteInfoParseResult parseLiteInfoResponse(const json& value)
{
    auto result = makeResult<LiteInfoParseResult>(LiteResultCommand::Info);
    if (!requireObject(value, "$", result)) return result;

    if (!readOptionalStringField(value, "chain_name", result.info.chainName, result)) return result;
    if (!readOptionalStringField(value, "version", result.info.version, result)) return result;
    if (!readOptionalStringField(value, "vendor", result.info.vendor, result)) return result;
    if (!readOptionalSignedField(value, "latest_block_height", result.info.latestBlockHeight, result)) return result;
    if (!readOptionalSignedField(value, "difficulty", result.info.difficulty, result)) return result;
    if (!readOptionalSignedField(value, "longestchain", result.info.longestChain, result)) return result;
    if (!readOptionalSignedField(value, "notarized", result.info.notarized, result)) return result;
    if (!result.info.latestBlockHeight.has_value()) {
        fail(result, LiteResultParserError::MissingField, fieldPath("latest_block_height"), "info response is missing latest_block_height");
        return result;
    }

    succeed(result);
    return result;
}

LiteHeightParseResult parseLiteHeightResponse(const std::string& jsonText)
{
    auto result = makeResult<LiteHeightParseResult>(LiteResultCommand::Height);
    json parsed;
    if (!parseJsonText(jsonText, result, parsed)) return result;
    return parseLiteHeightResponse(parsed);
}

LiteHeightParseResult parseLiteHeightResponse(const json& value)
{
    auto result = makeResult<LiteHeightParseResult>(LiteResultCommand::Height);
    std::int64_t parsedHeight = 0;
    if (jsonToSigned(value, parsedHeight)) {
        result.height.height = parsedHeight;
        succeed(result);
        return result;
    }

    if (!requireObject(value, "$", result)) return result;
    for (const std::string key : {"height", "latest_block_height", "block_height"}) {
        if (!value.contains(key) || value.at(key).is_null()) continue;
        if (!jsonToSigned(value.at(key), parsedHeight)) {
            fail(result, LiteResultParserError::InvalidFieldValue, fieldPath(key), "height field is not an integer-compatible value");
            return result;
        }
        result.height.height = parsedHeight;
        succeed(result);
        return result;
    }

    fail(result, LiteResultParserError::MissingField, "$", "height response has no recognized height field");
    return result;
}

LiteBalanceParseResult parseLiteBalanceResponse(const std::string& jsonText)
{
    auto result = makeResult<LiteBalanceParseResult>(LiteResultCommand::Balance);
    json parsed;
    if (!parseJsonText(jsonText, result, parsed)) return result;
    return parseLiteBalanceResponse(parsed);
}

LiteBalanceParseResult parseLiteBalanceResponse(const json& value)
{
    auto result = makeResult<LiteBalanceParseResult>(LiteResultCommand::Balance);
    if (!requireObject(value, "$", result)) return result;
    if (!readRequiredUnsignedField(value, "tbalance", result.balance.transparentBalance, result)) return result;
    if (!readRequiredUnsignedField(value, "zbalance", result.balance.shieldedBalance, result)) return result;
    if (!readRequiredUnsignedField(value, "unconfirmed", result.balance.unconfirmedBalance, result)) return result;
    if (!readRequiredUnsignedField(value, "verified_zbalance", result.balance.verifiedShieldedBalance, result)) return result;
    if (!readRequiredUnsignedField(value, "spendable_zbalance", result.balance.spendableShieldedBalance, result)) return result;
    succeed(result);
    return result;
}

LiteAddressesParseResult parseLiteAddressesResponse(const std::string& jsonText)
{
    auto result = makeResult<LiteAddressesParseResult>(LiteResultCommand::Addresses);
    json parsed;
    if (!parseJsonText(jsonText, result, parsed)) return result;
    return parseLiteAddressesResponse(parsed);
}

LiteAddressesParseResult parseLiteAddressesResponse(const json& value)
{
    auto result = makeResult<LiteAddressesParseResult>(LiteResultCommand::Addresses);
    if (!requireObject(value, "$", result)) return result;
    if (!readStringArrayField(value, "z_addresses", result.addresses.zAddresses, result)) return result;
    if (!readStringArrayField(value, "t_addresses", result.addresses.tAddresses, result)) return result;
    succeed(result);
    return result;
}

LiteNotesParseResult parseLiteNotesResponse(const std::string& jsonText)
{
    auto result = makeResult<LiteNotesParseResult>(LiteResultCommand::Notes);
    json parsed;
    if (!parseJsonText(jsonText, result, parsed)) return result;
    return parseLiteNotesResponse(parsed);
}

LiteNotesParseResult parseLiteNotesResponse(const json& value)
{
    auto result = makeResult<LiteNotesParseResult>(LiteResultCommand::Notes);
    if (!requireObject(value, "$", result)) return result;
    if (!parseSpendableOutputArray(value, "unspent_notes", LiteSpendableOutputKind::UnspentNote, result.notes.unspentNotes, result)) return result;
    if (!parseSpendableOutputArray(value, "utxos", LiteSpendableOutputKind::Utxo, result.notes.utxos, result)) return result;
    if (!parseSpendableOutputArray(value, "pending_notes", LiteSpendableOutputKind::PendingNote, result.notes.pendingNotes, result)) return result;
    if (!parseSpendableOutputArray(value, "pending_utxos", LiteSpendableOutputKind::PendingUtxo, result.notes.pendingUtxos, result)) return result;
    succeed(result);
    return result;
}

LiteTransactionsParseResult parseLiteTransactionsResponse(const std::string& jsonText)
{
    auto result = makeResult<LiteTransactionsParseResult>(LiteResultCommand::List);
    json parsed;
    if (!parseJsonText(jsonText, result, parsed)) return result;
    return parseLiteTransactionsResponse(parsed);
}

LiteTransactionsParseResult parseLiteTransactionsResponse(const json& value)
{
    auto result = makeResult<LiteTransactionsParseResult>(LiteResultCommand::List);
    if (!requireArray(value, "$", result)) return result;
    result.transactions.transactions.clear();
    for (std::size_t index = 0; index < value.size(); ++index) {
        LiteTransactionRecord record;
        if (!parseTransactionRecord(value[index], "$[" + std::to_string(index) + "]", record, result)) return result;
        result.transactions.transactions.push_back(std::move(record));
    }
    succeed(result);
    return result;
}

LiteSyncStatusParseResult parseLiteSyncStatusResponse(const std::string& jsonText)
{
    auto result = makeResult<LiteSyncStatusParseResult>(LiteResultCommand::SyncStatus);
    json parsed;
    if (!parseJsonText(jsonText, result, parsed)) return result;
    return parseLiteSyncStatusResponse(parsed);
}

LiteSyncStatusParseResult parseLiteSyncStatusResponse(const json& value)
{
    auto result = makeResult<LiteSyncStatusParseResult>(LiteResultCommand::SyncStatus);
    if (!requireObject(value, "$", result)) return result;

    // The backend reports "syncing" as a STRING ("true"/"false"). synced_blocks/total_blocks
    // are present only while actively syncing; an idle wallet returns just {"syncing":"false"}.
    std::string syncing = "false";
    if (!readOptionalStringField(value, "syncing", syncing, result)) return result;
    const bool isSyncing = (syncing == "true" || syncing == "1");

    if (isSyncing) {
        if (!readRequiredUnsignedField(value, "synced_blocks", result.syncStatus.syncedBlocks, result)) return result;
        if (!readRequiredUnsignedField(value, "total_blocks", result.syncStatus.totalBlocks, result)) return result;
        result.syncStatus.complete = false;  // actively syncing
        result.syncStatus.progress = result.syncStatus.totalBlocks > 0
            ? std::min(1.0, static_cast<double>(result.syncStatus.syncedBlocks) /
                            static_cast<double>(result.syncStatus.totalBlocks))
            : 0.0;
    } else {
        // Not actively syncing: the backend reports no block counts. Treat as idle/caught-up.
        result.syncStatus.syncedBlocks = 0;
        result.syncStatus.totalBlocks = 0;
        result.syncStatus.complete = true;
        result.syncStatus.progress = 1.0;
    }
    succeed(result);
    return result;
}

} // namespace dragonx::wallet