#pragma once

#include "wallet_state.h"

#include <array>
#include <cstdint>
#include <ctime>
#include <string>
#include <unordered_map>
#include <vector>

struct sqlite3;

namespace dragonx {
namespace data {

class TransactionHistoryCache {
public:
    struct LoadResult {
        bool loaded = false;
        bool invalidated = false;
        int tipHeight = 0;
        std::string tipHash;
        std::time_t updatedAt = 0;
        std::vector<TransactionInfo> transactions;
        std::unordered_map<std::string, int> shieldedScanHeights;
    };

    TransactionHistoryCache();
    explicit TransactionHistoryCache(std::string databasePath);
    ~TransactionHistoryCache();

    TransactionHistoryCache(const TransactionHistoryCache&) = delete;
    TransactionHistoryCache& operator=(const TransactionHistoryCache&) = delete;

    static std::string defaultDatabasePath();
    static std::string walletIdentityFromAddresses(const std::vector<std::string>& shieldedAddresses,
                                                   const std::vector<std::string>& transparentAddresses);
    static std::string walletIdentityHash(const std::string& walletIdentity);

    bool ensureOpen();
    bool unlockWithPassphrase(const std::string& walletIdentity, const std::string& passphrase);
    void lockKey();
    bool hasKey() const { return key_ready_; }
    bool isUnlockedFor(const std::string& walletIdentity) const;

    LoadResult load(const std::string& walletIdentity,
                    int currentTipHeight,
                    const std::string& currentTipHash);
    bool replace(const std::string& walletIdentity,
                 int tipHeight,
                 const std::string& tipHash,
                 const std::vector<TransactionInfo>& transactions,
                 std::time_t updatedAt,
                 const std::unordered_map<std::string, int>& shieldedScanHeights = {});
    void clearWallet(const std::string& walletIdentity);
    int snapshotCount();

private:
    bool exec(const char* sql);
    bool createSchema();
    std::vector<unsigned char> getOrCreateSalt(const std::string& walletHash);
    bool deriveKey(const std::string& passphrase,
                   const std::vector<unsigned char>& salt);
    bool encryptPayload(const std::string& walletHash,
                        const std::string& plainText,
                        std::vector<unsigned char>& nonce,
                        std::vector<unsigned char>& cipherText) const;
    bool decryptPayload(const std::string& walletHash,
                        const std::vector<unsigned char>& nonce,
                        const std::vector<unsigned char>& cipherText,
                        std::string& plainText) const;
    bool readSnapshot(const std::string& walletHash,
                      int& tipHeight,
                      std::string& tipHash,
                      std::time_t& updatedAt,
                      std::vector<unsigned char>& nonce,
                      std::vector<unsigned char>& cipherText);
    void clearWalletByHash(const std::string& walletHash);
    // Delete snapshot + salt rows for every wallet hash except the live one, bounding the
    // DB so generating a new address (which changes the hash) doesn't orphan history forever.
    void pruneOtherWallets(const std::string& keepWalletHash);
    void close();

    sqlite3* db_ = nullptr;
    std::string database_path_;
    std::array<unsigned char, 32> key_{};
    bool key_ready_ = false;
    std::string unlocked_wallet_hash_;
};

} // namespace data
} // namespace dragonx
