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

#include "key_export_dialog.h"
#include "../../app.h"
#include "../../wallet/lite_wallet_controller.h"
#include <sodium.h>   // sodium_memzero — wipe transient secret copies (B7)
#include <nlohmann/json.hpp>
#include "../../rpc/rpc_client.h"
#include "../../rpc/rpc_worker.h"
#include "../../util/i18n.h"
#include "../schema/ui_schema.h"
#include "../material/draw_helpers.h"
#include "../material/type.h"
#include "../material/colors.h"
#include "../layout.h"
#include "../widgets/qr_code.h"
#include "../widgets/copy_field.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"

namespace dragonx {
namespace ui {

// Static member initialization
bool KeyExportDialog::s_open = false;
bool KeyExportDialog::s_fetching = false;
bool KeyExportDialog::s_show_key = false;
bool KeyExportDialog::s_show_qr = false;
std::uintptr_t KeyExportDialog::s_qr_tex = 0;
std::string KeyExportDialog::s_qr_cached;
KeyExportDialog::KeyType KeyExportDialog::s_key_type = KeyExportDialog::KeyType::Private;
std::string KeyExportDialog::s_address;
std::string KeyExportDialog::s_key;
std::string KeyExportDialog::s_error;

void KeyExportDialog::releaseQr()
{
    if (s_qr_tex) { FreeQRTexture(s_qr_tex); s_qr_tex = 0; }
    wallet::secureWipeLiteSecret(s_qr_cached);   // held a plaintext copy of the key for the QR
    s_show_qr = false;
}

void KeyExportDialog::show(const std::string& address, KeyType type)
{
    s_open = true;
    s_fetching = false;
    s_show_key = false;
    releaseQr();
    s_key_type = type;
    s_address = address;
    wallet::secureWipeLiteSecret(s_key);   // zero any prior secret before reuse
    s_error.clear();
}

bool KeyExportDialog::isOpen()
{
    return s_open;
}

void KeyExportDialog::hide()
{
    s_open = false;
    s_fetching = false;
    wallet::secureWipeLiteSecret(s_key);   // zero the displayed secret, don't just drop the buffer
    s_show_key = false;
    s_error.clear();
    releaseQr();
}

void KeyExportDialog::render(App* app)
{
    if (!s_open) return;

    const char* title = (s_key_type == KeyType::Private) ?
        TR("export_private_key") : TR("export_viewing_key");

    auto& S = schema::UI();
    auto addrInput = S.input("dialogs.key-export", "address-input");
    auto revealBtn = S.button("dialogs.key-export", "reveal-button");
    auto keyDisplay = S.drawElement("dialogs.key-export", "key-display");
    auto copyBtn = S.button("dialogs.key-export", "copy-button");
    auto closeBtn = S.button("dialogs.key-export", "close-button");
    (void)addrInput;
    (void)keyDisplay;
    (void)copyBtn;

    // Size the card to the address field's own natural width — the widest deterministic element before
    // the key is revealed — so the field fills the card and sits under its left-aligned "Address:" label
    // instead of floating centered in an over-wide card. This adapts to the address type (z vs t) and the
    // font scale automatically (no magic width). Mirrors AddressCopyField's boxW math; the revealed
    // key+QR layout adapts to whatever width this yields (it stacks the QR when narrow). Bounded to 85%
    // of the window as an upper safety limit. BeginOverlayDialog re-applies dpiScale(), so divide it out.
    const std::string chunkedAddr = widgets::ChunkString(s_address, 4);
    const float addrPadX = ImGui::GetStyle().FramePadding.x + 4.0f;   // matches AddressCopyField
    const float addrBoxW = ImGui::CalcTextSize(chunkedAddr.c_str()).x + addrPadX * 2.0f + 2.0f;
    // The BlurFloat overlay card insets content by 28px per side (draw_helpers BeginOverlayDialog); add
    // that (+ a little slack) so the address box fits on one line under its label instead of wrapping.
    const float wantW    = addrBoxW + 28.0f * 2.0f + 12.0f;
    const float maxW     = 0.85f * ImGui::GetMainViewport()->Size.x;
    const float cardW    = (wantW < maxW ? wantW : maxW) / Layout::dpiScale();
    material::OverlayDialogSpec ov;
    ov.title = title; ov.p_open = &s_open;
    ov.style = material::OverlayStyle::BlurFloat;
    ov.cardWidth = cardW; ov.idSuffix = "keyexport";
    if (material::BeginOverlayDialog(ov)) {
        material::DialogWarningHeader((s_key_type == KeyType::Private)
            ? TR("key_export_private_warning") : TR("key_export_viewing_warning"));

        ImGui::Spacing();

        // Address — click-to-copy field (chunked, width-clamped, centered).
        ImGui::Text("%s", TR("address_label"));
        widgets::AddressCopyField("##addrcopy", s_address);

        ImGui::Spacing();

        // Key display section
        const char* key_label = (s_key_type == KeyType::Private) ? TR("key_export_private_key") : TR("key_export_viewing_key");
        ImGui::Text("%s", key_label);

        if (s_fetching) {
            ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "%s", TR("key_export_fetching"));
        } else if (!s_error.empty()) {
            ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), TR("error_format"), s_error.c_str());
            // Not a dead-end: clearing the error falls back through to the Reveal button next frame.
            ImGui::Spacing();
            if (material::TactileButton(TR("retry"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
                s_error.clear();
            }
        } else if (s_key.empty() && s_address.length() < 26) {
            // Guard against an empty/blatantly-malformed address before dispatching a doomed reveal.
            // (Also avoids reading s_address[0] on an empty string in the Reveal branch below.)
            ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s", TR("invalid_address"));
        } else if (s_key.empty()) {
            // Show button to fetch key
            if (material::TactileButton(TR("key_export_reveal"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
                s_fetching = true;

                // Check if z-address or t-address
                bool is_zaddress = (s_address.length() > 50 || s_address[0] == 'z');

                if (auto* lite = app->liteWallet()) {
                    // Lite wallet: there is no daemon RPC — export the key locally via the SDXL
                    // backend (a local, network-free op) instead of the z_exportkey/dumpprivkey
                    // RPCs, which fail with "Not connected" when no full node is present.
                    const bool wantViewing = (s_key_type != KeyType::Private);
                    if (wantViewing && !is_zaddress) {
                        s_error = TR("key_export_viewing_keys_zonly");
                        s_fetching = false;
                    } else {
                        auto r = lite->exportPrivateKeys(s_address);
                        std::string found;
                        if (r.ok) {
                            try {
                                auto j = nlohmann::json::parse(r.privateKeysJson);
                                const nlohmann::json* entry = nullptr;
                                if (j.is_array()) {
                                    for (auto& e : j)
                                        if (e.contains("address") && e["address"] == s_address) { entry = &e; break; }
                                    if (!entry && !j.empty()) entry = &j.front();
                                } else if (j.is_object()) {
                                    entry = &j;
                                }
                                const char* field = wantViewing ? "viewing_key" : "private_key";
                                if (entry && entry->contains(field) && (*entry)[field].is_string())
                                    found = (*entry)[field].get<std::string>();
                            } catch (...) {}
                            wallet::secureWipeLiteSecret(r.privateKeysJson);
                        }
                        if (!found.empty()) {
                            s_key = found;
                            s_show_key = wantViewing;  // viewing keys are less sensitive
                        } else {
                            s_error = r.ok ? std::string(TR("grpc_key_not_available")) : r.error;
                        }
                        wallet::secureWipeLiteSecret(found);
                        s_fetching = false;
                    }
                } else if (s_key_type == KeyType::Private) {
                    // Export private key
                    std::string addr = s_address;
                    std::string method = is_zaddress ? "z_exportkey" : "dumpprivkey";
                    if (app->worker()) {
                        app->worker()->post([rpc = app->rpc(), addr, method]() -> rpc::RPCWorker::MainCb {
                            std::string key;
                            std::string error;
                            try {
                                rpc::RPCClient::TraceScope trace("Settings / Export key");
                                key = rpc->callSecretString(method, {addr});   // scrubs raw body + json node (B7)
                            } catch (const std::exception& e) {
                                error = e.what();
                            }
                            return [key = std::move(key), error]() mutable {
                                if (error.empty()) {
                                    s_key = key;
                                    s_show_key = false;  // Don't show by default
                                } else {
                                    s_error = error;
                                }
                                if (!key.empty()) sodium_memzero(&key[0], key.size());  // wipe the last transient copy
                                s_fetching = false;
                            };
                        });
                    }
                } else {
                    // Export viewing key (only for z-addresses)
                    if (is_zaddress) {
                        std::string addr = s_address;
                        if (app->worker()) {
                            app->worker()->post([rpc = app->rpc(), addr]() -> rpc::RPCWorker::MainCb {
                                std::string key;
                                std::string error;
                                try {
                                    rpc::RPCClient::TraceScope trace("Settings / Export viewing key");
                                    key = rpc->callSecretString("z_exportviewingkey", {addr});  // scrubs raw body + json node (B7)
                                } catch (const std::exception& e) {
                                    error = e.what();
                                }
                                return [key = std::move(key), error]() mutable {
                                    if (error.empty()) {
                                        s_key = key;
                                        s_show_key = true;  // Viewing keys are less sensitive
                                    } else {
                                        s_error = error;
                                    }
                                    if (!key.empty()) sodium_memzero(&key[0], key.size());  // wipe the last transient copy
                                    s_fetching = false;
                                };
                            });
                        }
                    } else {
                        s_error = TR("key_export_viewing_keys_zonly");
                        s_fetching = false;
                    }
                }
            }
            ImGui::SameLine();
            ImGui::TextDisabled("%s", TR("key_export_click_retrieve"));
        } else {
            // Key fetched. Layout: [ key text (click-to-copy) + Show/Hide below ]  [ QR | square ].
            const float dp    = Layout::dpiScale();
            const float gap   = 12.0f * dp;
            const float avail = ImGui::GetContentRegionAvail().x;
            // Larger, responsive QR: ~30% of the content width, clamped to a comfortable range.
            float qrSize = avail * 0.30f;
            if (qrSize < 200.0f * dp) qrSize = 200.0f * dp;
            if (qrSize > 340.0f * dp) qrSize = 340.0f * dp;
            float keyW = avail - qrSize - gap;
            const bool sideBySide = keyW >= 200.0f * dp;   // too narrow -> stack the QR under the key
            if (!sideBySide) keyW = avail;

            // Chunk for readability, but keep a Bech32 HRP (e.g. "secret-extended-key-main") intact
            // rather than slicing it into 4s — only the part from the '1' separator on is chunked.
            auto chunkKey = [](const std::string& s) -> std::string {
                const size_t one = s.find('1');
                if (one != std::string::npos && one > 0) {
                    bool isHrp = true;
                    for (size_t i = 0; i < one; ++i) {
                        const char c = s[i];
                        if (!((c >= 'a' && c <= 'z') || c == '-')) { isHrp = false; break; }
                    }
                    if (isHrp) return s.substr(0, one) + " " + widgets::ChunkString(s.substr(one), 4);
                }
                return widgets::ChunkString(s, 4);
            };
            const std::string shown   = s_show_key ? s_key : std::string(s_key.size(), '*');
            const std::string keyText = chunkKey(shown);

            // Left column: key text (click-to-copy), with the Show/Hide button just below it.
            ImGui::BeginGroup();
            if (widgets::CopyField("##keycopy", keyText, keyW, 0.0f, /*center=*/true)) {
                app->copySecretToClipboard(s_key);  // auto-clearing clipboard
            }
            ImGui::Spacing();
            if (material::TactileButton(s_show_key ? TR("hide") : TR("show"), ImVec2(0, 0),
                                       material::Type().subtitle1())) {
                s_show_key = !s_show_key;
            }
            ImGui::EndGroup();

            if (sideBySide) ImGui::SameLine(0, gap);
            else            ImGui::Spacing();

            // QR to the right of the key when revealed; an empty placeholder square while hidden.
            const ImVec2 sq0 = ImGui::GetCursorScreenPos();
            if (s_show_key && !s_key.empty()) {
                if (s_qr_cached != s_key) {  // (re)generate the QR texture when the key changes
                    if (s_qr_tex) { FreeQRTexture(s_qr_tex); s_qr_tex = 0; }
                    int qw = 0, qh = 0;
                    s_qr_tex = GenerateQRTexture(s_key.c_str(), &qw, &qh);
                    s_qr_cached = s_key;
                }
                if (s_qr_tex) {
                    // White quiet-zone backing so the code stays scannable on dark themes.
                    ImGui::GetWindowDrawList()->AddRectFilled(
                        sq0, ImVec2(sq0.x + qrSize, sq0.y + qrSize), IM_COL32_WHITE, 4.0f);
                    RenderQRCode(s_qr_tex, qrSize);
                } else {
                    ImGui::Dummy(ImVec2(qrSize, qrSize));
                }
            } else {
                // Empty placeholder square — same footprint the QR will occupy once revealed.
                ImGui::Dummy(ImVec2(qrSize, qrSize));
                ImDrawList* dl = ImGui::GetWindowDrawList();
                const ImVec2 sq1(sq0.x + qrSize, sq0.y + qrSize);
                dl->AddRectFilled(sq0, sq1, material::WithAlpha(material::OnSurface(), 8), 6.0f);
                dl->AddRect(sq0, sq1, material::WithAlpha(material::OnSurface(), 45), 6.0f, 0, 1.0f);
            }
        }

        // Close button — centered via the shared footer helper (no divider, matching the
        // dialog's prior leading-Spacing placement) instead of hand-computing the offset.
        float button_width = closeBtn.width;
        material::BeginOverlayDialogFooter(button_width, /*drawSeparator=*/false);

        if (material::TactileButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
            s_open = false;
            // Zero the secret, don't just drop the buffer (leaves the key in freed heap otherwise).
            wallet::secureWipeLiteSecret(s_key);
            s_show_key = false;
            releaseQr();
        }

        material::EndOverlayDialog();
    }

    // Dialog dismissed any other way (scrim click / Esc): wipe the key + its QR texture.
    if (!s_open) {
        wallet::secureWipeLiteSecret(s_key);
        s_show_key = false;
        releaseQr();
    }
}

} // namespace ui
} // namespace dragonx
