// Tests for the copy on the address-removal confirmation (issue #162).
//
// The screen's whole job is to warn before a destructive-looking action, so
// the copy is the substance and is tested as such. Two things it must not
// get wrong: what it takes to get the address back — the app refuses both
// obvious routes — and what counts as holding something, which is any
// ERC-20 as well as ETH, at any size, including a balance that rounds to
// zero at the four decimals the balance lines render. The DOM behaviour
// around them is driven against the real popup by tests/e2e/run.js.

// helpers.js pulls in state.js, which reads chrome.storage.local at load.
globalThis.chrome = {
    storage: { local: { get: async () => ({}), set: async () => {} } },
};

const { addressHoldsFunds } = require("../src/popup/views/helpers");
const {
    recoveryPathText,
    balanceWarningHtml,
} = require("../src/popup/views/deleteAddress");
const { prices, clearPrices } = require("../src/shared/prices");
const { state } = require("../src/shared/state");

// The screen prices holdings, and pricing asks which chain it is on. Reading
// the singleton's network before anything loaded it now throws rather than
// answering mainnet by default
// (https://git.eeqj.de/sneak/AutistMask/issues/324), so the network this
// fixture is on is stated instead of assumed.
state.networkId = "mainnet";

const USDC = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";

const EMPTY = { address: "0x1", balance: "0.0000", tokenBalances: [] };
const ETH_ONLY = { address: "0x1", balance: "1.5", tokenBalances: [] };
const DUST = { address: "0x1", balance: "0.00001", tokenBalances: [] };
const TOKEN_ONLY = {
    address: "0x1",
    balance: "0.0000",
    tokenBalances: [{ address: USDC, symbol: "USDC", balance: "2500.0" }],
};
const ZERO_TOKEN = {
    address: "0x1",
    balance: "0",
    tokenBalances: [{ address: USDC, symbol: "USDC", balance: "0" }],
};

afterEach(() => {
    clearPrices();
});

describe("what the screen says it takes to get the address back", () => {
    // The screen used to promise the address "can be brought back at any
    // time by importing this wallet's recovery phrase again". That import is
    // refused as a duplicate for as long as the wallet is present, which it
    // always is here — a wallet never gives up its last address.
    test("it does not promise a re-import while the wallet is here", () => {
        const text = recoveryPathText({ type: "hd" });
        expect(text).not.toMatch(/at any time/);
        expect(text).toContain("is refused while this wallet is still here");
    });

    test("it names deleting the whole wallet as the route back", () => {
        expect(recoveryPathText({ type: "hd" })).toContain(
            "delete the whole wallet in Settings",
        );
    });

    // The scan after a re-import finds used addresses only, so an address
    // that never saw a transaction does not come back at all. Saying so is
    // the difference between a warning and a false reassurance.
    test("it states the limit: only on-chain activity is found", () => {
        const text = recoveryPathText({ type: "hd" });
        expect(text).toContain("only finds addresses that have on-chain");
        expect(text).toContain("never been used is not found by it");
    });

    // The screen is offered on xprv wallets too, and an xprv wallet holds no
    // recovery phrase — telling its owner to import one would send them
    // looking for words that do not exist.
    test("an xprv wallet is told about its extended private key", () => {
        const text = recoveryPathText({ type: "xprv" });
        expect(text).toContain("extended private key");
        expect(text).not.toContain("recovery phrase");
    });

    test("an HD wallet is told about its recovery phrase", () => {
        const text = recoveryPathText({ type: "hd" });
        expect(text).toContain("recovery phrase");
        expect(text).not.toContain("extended private key");
    });
});

describe("whether an address holds anything", () => {
    test("ETH counts", () => {
        expect(addressHoldsFunds(ETH_ONLY)).toBe(true);
    });

    // The case that decides the screen: no ETH at all, and $2500 of a
    // stablecoin sitting at the address.
    test("an ERC-20 balance counts even with no ETH", () => {
        expect(addressHoldsFunds(TOKEN_ONLY)).toBe(true);
    });

    // 0.00001 ETH renders as "0.0000" at four decimals. It is still money.
    test("an ETH balance below the displayed precision counts", () => {
        expect(addressHoldsFunds(DUST)).toBe(true);
    });

    test("an address holding nothing does not", () => {
        expect(addressHoldsFunds(EMPTY)).toBe(false);
        expect(addressHoldsFunds(ZERO_TOKEN)).toBe(false);
    });

    test("a missing address or missing fields do not", () => {
        expect(addressHoldsFunds(undefined)).toBe(false);
        expect(addressHoldsFunds({ address: "0x1" })).toBe(false);
    });
});

describe("the balance warning on the removal confirmation", () => {
    test("an address holding nothing gets a blank line, not a warning", () => {
        expect(balanceWarningHtml(EMPTY)).toBe("&nbsp;");
        expect(balanceWarningHtml(ZERO_TOKEN)).toBe("&nbsp;");
    });

    test("an ERC-20-only address is warned about, and its token listed", () => {
        const html = balanceWarningHtml(TOKEN_ONLY);
        expect(html).toContain("This address holds a balance.");
        expect(html).toContain("does not move or spend anything");
        expect(html).toContain("USDC");
        expect(html).toContain("2500.0000");
    });

    // The rendered line says 0.0000 for this address — that is the display
    // format, shared with Home and AddressDetail — and the warning is shown
    // all the same, because the balance is not zero.
    test("an ETH balance that renders as 0.0000 is warned about", () => {
        const html = balanceWarningHtml(DUST);
        expect(html).toContain("This address holds a balance.");
        expect(html).toContain("<span>0.0000</span>");
    });

    // The sentence must not assert an amount, because any amount it could
    // assert has been rounded: "This address holds 0.0000 ETH." is what the
    // rounded form produces for an address that holds real money.
    test("the warning sentence asserts no rounded amount", () => {
        for (const addr of [DUST, ETH_ONLY, TOKEN_ONLY]) {
            expect(balanceWarningHtml(addr)).not.toMatch(
                /holds [\d.]+ (ETH|USDC)/,
            );
        }
    });

    test("the USD total is shown when prices are known", () => {
        prices.ETH = 2000;
        prices.USDC = 1;
        expect(balanceWarningHtml(TOKEN_ONLY)).toContain("Total: $2,500.00");
        expect(balanceWarningHtml(ETH_ONLY)).toContain("Total: $3,000.00");
    });

    // getAddressValue() reports no value on testnet and before the first
    // price fetch. A "Total: $0.00" there would be a lie about the holdings.
    test("no USD total is shown when prices are not known", () => {
        expect(balanceWarningHtml(TOKEN_ONLY)).not.toContain("Total:");
    });
});
