#include "wallet/lite_wallet_state_mapper.h"

#include <utility>

namespace dragonx::wallet {
namespace {

LiteWalletAppAddressModel mapAddress(const std::string& address, LiteWalletAppAddressKind kind)
{
    LiteWalletAppAddressModel model;
    model.address = address;
    model.kind = kind;
    return model;
}

LiteWalletAppSpendableOutputModel mapSpendableOutput(const LiteSpendableOutput& output)
{
    LiteWalletAppSpendableOutputModel model;
    model.kind = output.kind;
    model.address = output.address;
    model.createdInTxid = output.createdInTxid;
    model.createdInBlock = output.createdInBlock;
    model.valueZatoshis = output.value;
    model.spent = output.spent;
    model.unconfirmedSpent = output.unconfirmedSpent;
    model.pending = output.pending;
    model.spendable = output.spendable;
    return model;
}

LiteWalletAppTransactionKind mapTransactionKind(LiteTransactionDirection direction)
{
    switch (direction) {
        case LiteTransactionDirection::Send: return LiteWalletAppTransactionKind::Send;
        case LiteTransactionDirection::Receive: return LiteWalletAppTransactionKind::Receive;
        case LiteTransactionDirection::Unknown: return LiteWalletAppTransactionKind::Unknown;
    }
    return LiteWalletAppTransactionKind::Unknown;
}

LiteWalletAppTransactionOutputModel mapTransactionOutput(const LiteTransactionOutput& output)
{
    LiteWalletAppTransactionOutputModel model;
    model.address = output.address;
    model.valueZatoshis = output.value;
    model.memo = output.memo;
    return model;
}

LiteWalletAppTransactionModel mapTransaction(const LiteTransactionRecord& transaction)
{
    LiteWalletAppTransactionModel model;
    model.txid = transaction.txid;
    model.kind = mapTransactionKind(transaction.direction);
    model.timestamp = transaction.datetime;
    model.blockHeight = transaction.blockHeight;
    model.unconfirmed = transaction.unconfirmed;
    model.address = transaction.address;
    model.amountZatoshis = transaction.amount;
    model.signedAmountZatoshis = model.kind == LiteWalletAppTransactionKind::Send
        ? -transaction.amount
        : transaction.amount;
    model.memo = transaction.memo;
    model.position = transaction.position;

    model.outgoingOutputs.reserve(transaction.outgoingMetadata.size());
    for (const auto& output : transaction.outgoingMetadata) {
        model.outgoingOutputs.push_back(mapTransactionOutput(output));
    }
    return model;
}

bool modelHasAnyMappedField(const LiteWalletAppRefreshModel& model)
{
    return model.hasChainInfo || model.hasHeight || model.hasBalance || model.hasAddresses ||
           model.hasSpendableOutputs || model.hasTransactions || model.hasSyncStatus;
}

void addIssue(LiteWalletStateMapResult& result, LiteWalletStateMapIssue issue, std::string message)
{
    result.issues.push_back(LiteWalletStateMapIssueInfo{issue, std::move(message)});
}

} // namespace

const char* liteWalletAppAddressKindName(LiteWalletAppAddressKind kind)
{
    switch (kind) {
        case LiteWalletAppAddressKind::Shielded: return "shielded";
        case LiteWalletAppAddressKind::Transparent: return "transparent";
    }
    return "unknown";
}

const char* liteWalletAppTransactionKindName(LiteWalletAppTransactionKind kind)
{
    switch (kind) {
        case LiteWalletAppTransactionKind::Unknown: return "unknown";
        case LiteWalletAppTransactionKind::Send: return "send";
        case LiteWalletAppTransactionKind::Receive: return "receive";
    }
    return "unknown";
}

const char* liteWalletStateMapIssueName(LiteWalletStateMapIssue issue)
{
    switch (issue) {
        case LiteWalletStateMapIssue::EmptyBundle: return "EmptyBundle";
        case LiteWalletStateMapIssue::IncompleteBundle: return "IncompleteBundle";
    }
    return "Unknown";
}

LiteWalletStateMapResult mapLiteWalletRefreshBundle(const LiteWalletRefreshBundle& bundle)
{
    LiteWalletStateMapResult result;
    result.stateMutationAllowed = false;
    result.model.complete = bundle.complete;
    result.model.successfulCommandCount = bundle.successfulCommandCount;

    if (bundle.hasInfo) {
        result.model.hasChainInfo = true;
        result.model.chain.chainName = bundle.info.chainName;
        result.model.chain.version = bundle.info.version;
        result.model.chain.vendor = bundle.info.vendor;
        result.model.chain.latestBlockHeight = bundle.info.latestBlockHeight;
        result.model.chain.difficulty = bundle.info.difficulty;
        result.model.chain.longestChain = bundle.info.longestChain;
        result.model.chain.notarized = bundle.info.notarized;
    }

    if (bundle.hasHeight) {
        result.model.hasHeight = true;
        result.model.height.height = bundle.height.height;
    }

    if (bundle.hasBalance) {
        result.model.hasBalance = true;
        result.model.balance.transparentZatoshis = bundle.balance.transparentBalance;
        result.model.balance.shieldedZatoshis = bundle.balance.shieldedBalance;
        result.model.balance.unconfirmedZatoshis = bundle.balance.unconfirmedBalance;
        result.model.balance.verifiedShieldedZatoshis = bundle.balance.verifiedShieldedBalance;
        result.model.balance.spendableShieldedZatoshis = bundle.balance.spendableShieldedBalance;
        result.model.balance.totalZatoshis = bundle.balance.transparentBalance + bundle.balance.shieldedBalance;
    }

    if (bundle.hasAddresses) {
        result.model.hasAddresses = true;
        result.model.addresses.reserve(bundle.addresses.zAddresses.size() + bundle.addresses.tAddresses.size());
        for (const auto& address : bundle.addresses.zAddresses) {
            result.model.addresses.push_back(mapAddress(address, LiteWalletAppAddressKind::Shielded));
        }
        for (const auto& address : bundle.addresses.tAddresses) {
            result.model.addresses.push_back(mapAddress(address, LiteWalletAppAddressKind::Transparent));
        }
    }

    if (bundle.hasNotes) {
        result.model.hasSpendableOutputs = true;
        const auto outputCount = bundle.notes.unspentNotes.size() + bundle.notes.utxos.size() +
            bundle.notes.pendingNotes.size() + bundle.notes.pendingUtxos.size();
        result.model.spendableOutputs.reserve(outputCount);
        for (const auto& output : bundle.notes.unspentNotes) {
            result.model.spendableOutputs.push_back(mapSpendableOutput(output));
        }
        for (const auto& output : bundle.notes.utxos) {
            result.model.spendableOutputs.push_back(mapSpendableOutput(output));
        }
        for (const auto& output : bundle.notes.pendingNotes) {
            result.model.spendableOutputs.push_back(mapSpendableOutput(output));
        }
        for (const auto& output : bundle.notes.pendingUtxos) {
            result.model.spendableOutputs.push_back(mapSpendableOutput(output));
        }
    }

    if (bundle.hasTransactions) {
        result.model.hasTransactions = true;
        result.model.transactions.reserve(bundle.transactions.transactions.size());
        for (const auto& transaction : bundle.transactions.transactions) {
            result.model.transactions.push_back(mapTransaction(transaction));
        }
    }

    if (bundle.hasSyncStatus) {
        result.model.hasSyncStatus = true;
        result.model.sync.walletHeight = bundle.syncStatus.syncedBlocks;
        result.model.sync.chainHeight = bundle.syncStatus.totalBlocks;
        result.model.sync.progress = bundle.syncStatus.progress;
        result.model.sync.complete = bundle.syncStatus.complete;
    }

    if (!modelHasAnyMappedField(result.model)) {
        addIssue(result, LiteWalletStateMapIssue::EmptyBundle, "lite refresh bundle has no mappable fields");
        result.error = result.issues.back().message;
        return result;
    }

    if (!bundle.complete) {
        addIssue(result, LiteWalletStateMapIssue::IncompleteBundle, "lite refresh bundle is partial");
    }

    result.ok = true;
    return result;
}

LiteWalletStateMapResult mapLiteWalletRefreshResult(const LiteWalletRefreshResult& result)
{
    return mapLiteWalletRefreshBundle(result.bundle);
}

} // namespace dragonx::wallet