#pragma once

// DragonX Wallet - HushChat service: turns harvested memo metadata into decrypted, threaded
// messages. Owns the long-lived chat identity keypair (a secret) + the in-memory store.
// Move-disabled (the secret stays pinned); wipes the secret on destruction/clear.
// Not thread-safe — drive from the main thread (where refresh results are applied).

#include "chat_crypto.h"     // ChatKeyPair
#include "chat_protocol.h"   // HushChatTransactionMetadata
#include "chat_outgoing.h"   // OutgoingChatMemos, ChatComposeStatus
#include "chat_store.h"

#include <cstdint>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>

namespace dragonx::chat {

class ChatDatabase;  // optional persistent backing (Phase 2); set via setPersistence

class ChatService {
public:
    ChatService() = default;
    ~ChatService();
    ChatService(const ChatService&) = delete;
    ChatService& operator=(const ChatService&) = delete;
    ChatService(ChatService&&) = delete;
    ChatService& operator=(ChatService&&) = delete;

    // Provision (or replace) the chat identity. Copies the keypair — the caller should wipe
    // its own copy afterwards (see chat_identity.h ownership note).
    void setIdentity(const ChatKeyPair& keys);
    bool hasIdentity() const { return has_identity_; }
    void clearIdentity();  // wipes the held secret key

    // Decrypt/record each metadata entry (a Message is decrypted; a ContactRequest carries its
    // plaintext through) and thread it into the store. Each message is stamped with its own
    // transaction time via `txTimestamps` (keyed by txid), falling back to `fallbackTimestamp`
    // when the txid isn't present. Newly-added messages are also persisted (if a database is
    // attached). Returns the number of NEW messages added; 0 with no identity. Undecryptable
    // Messages are dropped silently (no logging of memo/plaintext).
    // When `newIncomingCids` is non-null it is filled with the conversation ids of the genuinely-new
    // incoming (non-request) messages appended this call — a reliable "a new message arrived here"
    // signal for notifications that doesn't depend on any timestamp/seen-watermark comparison.
    int ingest(const std::vector<HushChatTransactionMetadata>& metadata,
               const std::unordered_map<std::string, std::int64_t>& txTimestamps,
               std::int64_t fallbackTimestamp = 0,
               std::vector<std::string>* newIncomingCids = nullptr);

    // Attach a persistent backing store (Phase 2). Not owned. Pass nullptr to detach. New messages
    // from ingest() are written through; loadFromDatabase() rehydrates the in-memory store from it.
    void setPersistence(ChatDatabase* db) { db_ = db; }
    // Load previously-persisted messages (already decrypted at ingest, re-encrypted at rest under
    // the seed-derived key) into the in-memory store. No-op without an unlocked database.
    void loadFromDatabase();

    // Set a predicate returning true for a BLOCKED conversation id. ingest() drops those messages
    // entirely (they are never stored or persisted) until the predicate stops returning true — this is
    // how "delete & block" suppresses old and future messages. Typically wired to Settings::isChatBlocked.
    void setBlockedPredicate(std::function<bool(const std::string&)> pred) { blocked_pred_ = std::move(pred); }

    // Locally delete a conversation: remove its messages from the store and the database. When `block`
    // is false, the removed messages are tombstoned so a chain re-scan won't re-import them, but a
    // genuinely NEW message (new txid) revives the thread. When `block` is true, the rows are removed
    // WITHOUT a tombstone (the caller also records the cid as blocked via the predicate above); unblocking
    // later lets the conversation re-import from chain. Returns false (leaving the store untouched) if the
    // persisted rows couldn't be removed, so the caller can avoid a store/DB divergence.
    bool deleteConversation(const std::string& conversationId, bool block);

    // --- Outgoing (compose) ---
    // My chat public key (hex), or "" without an identity — goes in an outgoing header's "p".
    std::string identityPublicKeyHex() const;
    // Construct the outgoing memos for an ENCRYPTED message, using the held identity to encrypt.
    ChatComposeStatus composeMessage(const std::string& myReplyZaddr,
                                     const std::string& peerPublicKeyHex,
                                     const std::string& peerZaddr,
                                     const std::string& conversationId,
                                     const std::string& plaintext,
                                     OutgoingChatMemos& out) const;
    // Construct the outgoing memos for a plaintext contact request (no peer key needed yet).
    ChatComposeStatus composeContactRequest(const std::string& myReplyZaddr,
                                            const std::string& peerZaddr,
                                            const std::string& conversationId,
                                            const std::string& requestText,
                                            OutgoingChatMemos& out) const;
    // Echo a locally-composed outgoing message into the store (and DB). Returns true if new. (We
    // never harvest our own sent memos — they land on the peer's address — so this echo is the
    // only local record of what we sent.)
    bool recordOutgoing(const ChatMessage& message);

    // Two-phase echo for delivery tracking: record + persist immediately as Sending (survives an app
    // quit), then resolveOutgoing() upserts the final status once the broadcast completes. A stray
    // persisted Sending (crash mid-broadcast) loads back as Sent.
    bool recordOutgoingPending(const ChatMessage& message);
    void resolveOutgoing(const std::string& txid, ChatDelivery delivery);

    const ChatStore& store() const { return store_; }
    ChatStore& store() { return store_; }

private:
    ChatKeyPair identity_{};
    bool has_identity_ = false;
    ChatStore store_;
    ChatDatabase* db_ = nullptr;  // optional; not owned
    std::function<bool(const std::string&)> blocked_pred_;  // true => cid is blocked (drop its messages)
};

} // namespace dragonx::chat
