// Address-token detail view: shows a single token's balance and
// filtered transactions for the selected address.

const {
    $,
    showView,
    showFlash,
    flashCopyFeedback,
    addressDotHtml,
    addressTitle,
    escapeHtml,
    displaySymbol,
    truncateMiddle,
    balanceLine,
    unknownableAmount,
    renderAddressHtml,
    attachCopyHandlers,
    goBack,
    pushCurrentView,
} = require("./helpers");
const { state, saveState } = require("../../shared/state");
const { TOKEN_BY_ADDRESS, resolveSymbol } = require("../../shared/tokenList");
const { formatUsd, getPrice } = require("../../shared/prices");
const {
    fetchRecentTransactions,
    filterTransactions,
} = require("../../shared/transactions");
const { resolveEnsNames } = require("../../shared/ens");
const {
    updateSendBalance,
    renderSendTokenSelect,
    resetSendValidation,
} = require("./send");
const { log } = require("../../shared/log");
const makeBlockie = require("ethereum-blockies-base64");
const { walletDefect } = require("../../shared/walletDefects");

let ctx;

function isoDate(timestamp) {
    const d = new Date(timestamp * 1000);
    const pad = (n) => String(n).padStart(2, "0");
    if (state.utcTimestamps) {
        return (
            d.getUTCFullYear() +
            "-" +
            pad(d.getUTCMonth() + 1) +
            "-" +
            pad(d.getUTCDate()) +
            "T" +
            pad(d.getUTCHours()) +
            ":" +
            pad(d.getUTCMinutes()) +
            ":" +
            pad(d.getUTCSeconds()) +
            "Z"
        );
    }
    const offsetMin = -d.getTimezoneOffset();
    const sign = offsetMin >= 0 ? "+" : "-";
    const absOff = Math.abs(offsetMin);
    const tzStr = sign + pad(Math.floor(absOff / 60)) + ":" + pad(absOff % 60);
    return (
        d.getFullYear() +
        "-" +
        pad(d.getMonth() + 1) +
        "-" +
        pad(d.getDate()) +
        "T" +
        pad(d.getHours()) +
        ":" +
        pad(d.getMinutes()) +
        ":" +
        pad(d.getSeconds()) +
        tzStr
    );
}

function timeAgo(timestamp) {
    const seconds = Math.floor(Date.now() / 1000 - timestamp);
    if (seconds < 60) return seconds + " seconds ago";
    const minutes = Math.floor(seconds / 60);
    if (minutes < 60)
        return minutes + " minute" + (minutes !== 1 ? "s" : "") + " ago";
    const hours = Math.floor(minutes / 60);
    if (hours < 24) return hours + " hour" + (hours !== 1 ? "s" : "") + " ago";
    const days = Math.floor(hours / 24);
    if (days < 30) return days + " day" + (days !== 1 ? "s" : "") + " ago";
    const months = Math.floor(days / 30);
    if (months < 12)
        return months + " month" + (months !== 1 ? "s" : "") + " ago";
    const years = Math.floor(days / 365);
    return years + " year" + (years !== 1 ? "s" : "") + " ago";
}

let loadedTxs = [];
let ensNameMap = new Map();
let currentSymbol = null;

function show() {
    const wallet = state.wallets[state.selectedWallet];
    const addr = wallet.addresses[state.selectedAddress];
    const ai = state.selectedAddress;
    const tokenId = state.selectedToken;

    // Determine token symbol and balance
    let symbol, amount, price;
    const knownToken = TOKEN_BY_ADDRESS.get(tokenId.toLowerCase());
    if (tokenId === "ETH") {
        symbol = "ETH";
        amount = parseFloat(addr.balance || "0");
        price = getPrice("ETH");
    } else {
        const tb = (addr.tokenBalances || []).find(
            (t) => t.address.toLowerCase() === tokenId.toLowerCase(),
        );
        symbol = resolveSymbol(
            tokenId,
            addr.tokenBalances,
            state.trackedTokens,
        );
        // null when the scale is unknown: no quantity to show, and none to
        // price. balanceLine() states that rather than printing 0.0000.
        amount = tb ? unknownableAmount(tb.balance) : 0;
        price = getPrice(symbol);
    }

    currentSymbol = symbol;

    $("address-token-title").textContent =
        wallet.name +
        " \u2014 Address " +
        (ai + 1) +
        " \u2014 " +
        displaySymbol(symbol);

    // Blockie
    const blockieEl = $("address-token-jazzicon");
    blockieEl.innerHTML = "";
    const img = document.createElement("img");
    img.src = makeBlockie(addr.address);
    img.width = 48;
    img.height = 48;
    img.style.imageRendering = "pixelated";
    img.style.borderRadius = "50%";
    blockieEl.appendChild(img);

    // Address line
    const addrTitle = addressTitle(addr.address, state.wallets);
    $("address-token-line").innerHTML = renderAddressHtml(addr.address, {
        title: addrTitle,
        ensName: addr.ensName,
    });
    $("address-token-line").dataset.full = addr.address;
    attachCopyHandlers($("address-token-line"));

    // USD total for this token only
    const usdVal = price && amount !== null ? amount * price : null;
    const usdStr = formatUsd(usdVal);
    $("address-token-usd-total").innerHTML = usdStr || "&nbsp;";

    // Single token balance line (no tokenId — not clickable here)
    $("address-token-balance").innerHTML = balanceLine(symbol, amount, price);

    // Token contract details (ERC-20 only)
    const contractInfo = $("address-token-contract-info");
    if (tokenId !== "ETH") {
        const tb = (addr.tokenBalances || []).find(
            (t) => t.address.toLowerCase() === tokenId.toLowerCase(),
        );
        const tracked = (state.trackedTokens || []).find(
            (t) => t.address.toLowerCase() === tokenId.toLowerCase(),
        );
        const rawName =
            (tb && tb.name) ||
            (tracked && tracked.name) ||
            (knownToken && knownToken.name) ||
            null;
        const rawSymbol =
            (tb && tb.symbol) ||
            (tracked && tracked.symbol) ||
            (knownToken && knownToken.symbol) ||
            null;
        const tokenName = rawName ? escapeHtml(rawName) : null;
        const tokenSymbol = rawSymbol
            ? escapeHtml(displaySymbol(rawSymbol))
            : null;
        const tokenDecimals =
            tb && tb.decimals != null
                ? tb.decimals
                : tracked && tracked.decimals != null
                  ? tracked.decimals
                  : knownToken && knownToken.decimals != null
                    ? knownToken.decimals
                    : null;
        const tokenHolders = tb && tb.holders != null ? tb.holders : null;
        const projectUrl = knownToken && knownToken.url ? knownToken.url : null;
        let infoHtml = `<div class="font-bold mb-2">Contract Address</div>`;
        infoHtml += `<div class="mb-2">${renderAddressHtml(tokenId)}</div>`;
        if (tokenName)
            infoHtml += `<div class="mb-1"><span class="text-muted">Name:</span> ${tokenName}</div>`;
        if (tokenSymbol)
            infoHtml += `<div class="mb-1"><span class="text-muted">Symbol:</span> ${tokenSymbol}</div>`;
        if (tokenDecimals != null)
            infoHtml += `<div class="mb-1"><span class="text-muted">Decimals:</span> ${tokenDecimals}</div>`;
        if (tokenHolders != null)
            infoHtml += `<div class="mb-1"><span class="text-muted">Holders:</span> ${Number(tokenHolders).toLocaleString()}</div>`;
        if (projectUrl)
            infoHtml += `<div class="mb-1"><span class="text-muted">Website:</span> <a href="${escapeHtml(projectUrl)}" target="_blank" rel="noopener" class="underline decoration-dashed">${escapeHtml(projectUrl)}</a></div>`;
        contractInfo.innerHTML = infoHtml;
        attachCopyHandlers(contractInfo);
        contractInfo.classList.remove("hidden");
    } else {
        contractInfo.innerHTML = "";
        contractInfo.classList.add("hidden");
    }

    // Transactions
    $("address-token-tx-list").innerHTML =
        '<div class="text-muted text-xs py-1">Loading...</div>';
    showView("address-token");
    loadTransactions(addr.address, tokenId);
}

async function loadTransactions(address, tokenId) {
    try {
        const rawTxs = await fetchRecentTransactions(
            address,
            state.blockscoutUrl,
        );
        const result = filterTransactions(rawTxs, {
            hideSpoofedSymbols: state.hideSpoofedSymbols,
            hideLowHolderTokens: state.hideLowHolderTokens,
            hideFraudContracts: state.hideFraudContracts,
            hideDustTransactions: state.hideDustTransactions,
            dustThresholdGwei: state.dustThresholdGwei,
            fraudContracts: state.fraudContracts,
        });
        let txs = result.transactions;

        // Persist any newly discovered fraud contracts
        if (result.newFraudContracts.length > 0) {
            for (const addr of result.newFraudContracts) {
                if (!state.fraudContracts.includes(addr)) {
                    state.fraudContracts.push(addr);
                }
            }
            await saveState();
        }

        // Filter to this token only
        if (tokenId === "ETH") {
            txs = txs.filter((tx) => tx.contractAddress === null);
        } else {
            txs = txs.filter(
                (tx) =>
                    tx.contractAddress &&
                    tx.contractAddress.toLowerCase() === tokenId.toLowerCase(),
            );
        }

        loadedTxs = txs;

        // Collect ALL unique addresses for ENS resolution so reverse
        // lookups work for every displayed address.
        const counterparties = [
            ...new Set(txs.flatMap((tx) => [tx.from, tx.to].filter(Boolean))),
        ];
        if (counterparties.length > 0) {
            try {
                ensNameMap = await resolveEnsNames(
                    counterparties,
                    state.rpcUrl,
                    state.networkId,
                );
            } catch {
                ensNameMap = new Map();
            }
        }

        renderTransactions(txs);
    } catch (e) {
        log.errorf("loadTransactions failed:", e.message);
        $("address-token-tx-list").innerHTML =
            '<div class="text-muted text-xs py-1">Failed to load transactions.</div>';
    }
}

function renderTransactions(txs) {
    const list = $("address-token-tx-list");
    if (txs.length === 0) {
        list.innerHTML =
            '<div class="text-muted text-xs py-1">No transactions found.</div>';
        return;
    }
    let html = "";
    let i = 0;
    for (const tx of txs) {
        const counterparty = tx.direction === "sent" ? tx.to : tx.from;
        const ensName = ensNameMap.get(counterparty) || null;
        const title = addressTitle(counterparty, state.wallets);
        // The explorer's method name for a contract call, title-cased.
        const dirLabel = escapeHtml(tx.directionLabel);
        const sym = displaySymbol(tx.symbol);
        const amountStr = tx.value
            ? escapeHtml(tx.value + " " + sym)
            : escapeHtml(sym);
        const maxAddr = Math.max(32, 36 - Math.max(0, amountStr.length - 10));
        const displayAddr =
            title || ensName || truncateMiddle(counterparty, maxAddr);
        const addrStr = escapeHtml(displayAddr);
        const dot = addressDotHtml(counterparty);
        const err = tx.isError ? " (failed)" : "";
        const opacity = tx.isError ? " opacity:0.5;" : "";
        const ago = escapeHtml(timeAgo(tx.timestamp));
        const iso = escapeHtml(isoDate(tx.timestamp));
        html += `<div class="tx-row py-2 border-b border-border-light text-xs cursor-pointer hover:bg-hover" data-tx="${i}" style="${opacity}">`;
        html += `<div class="flex justify-between"><span class="text-muted" title="${iso}">${ago}</span><span>${dirLabel}${err}</span></div>`;
        html += `<div class="flex justify-between"><span class="flex items-center">${dot}${addrStr}</span><span>${amountStr}</span></div>`;
        html += `</div>`;
        i++;
    }
    list.innerHTML = html;
    list.querySelectorAll(".tx-row").forEach((row) => {
        row.addEventListener("click", () => {
            const idx = parseInt(row.dataset.tx, 10);
            const tx = loadedTxs[idx];
            tx.fromEns = ensNameMap.get(tx.from) || null;
            tx.toEns = ensNameMap.get(tx.to) || null;
            ctx.showTransactionDetail(tx);
        });
    });
}

function init(_ctx) {
    ctx = _ctx;
    $("address-token-contract-info").addEventListener("click", (e) => {
        const copyEl = e.target.closest("[data-copy]");
        if (copyEl) {
            navigator.clipboard.writeText(copyEl.dataset.copy);
            showFlash("Copied!");
            flashCopyFeedback(copyEl);
        }
    });

    $("btn-address-token-back").addEventListener("click", () => {
        goBack();
    });

    $("btn-address-token-send").addEventListener("click", () => {
        const defect = walletDefect(state.wallets[state.selectedWallet]);
        if (defect) {
            showFlash(defect.shortMessage);
            return;
        }
        const addr =
            state.wallets[state.selectedWallet].addresses[
                state.selectedAddress
            ];
        if (!addr.balance || parseFloat(addr.balance) === 0) {
            if (state.selectedToken === "ETH") {
                showFlash("Cannot send \u2014 zero balance.");
                return;
            }
        }
        renderSendTokenSelect(addr);
        $("send-to").value = "";
        $("send-amount").value = "";
        const tokenId = state.selectedToken;
        if (tokenId === "ETH") {
            $("send-token").value = "ETH";
        } else {
            $("send-token").value = tokenId;
        }
        // Hide dropdown, show static token display
        $("send-token").classList.add("hidden");
        let staticHtml = `<div class="font-bold">${escapeHtml(displaySymbol(currentSymbol))}</div>`;
        if (tokenId !== "ETH") {
            staticHtml += `<div class="text-xs">${renderAddressHtml(tokenId)}</div>`;
        }
        $("send-token-static").innerHTML = staticHtml;
        $("send-token-static").classList.remove("hidden");
        attachCopyHandlers($("send-token-static"));
        updateSendBalance();
        resetSendValidation();
        pushCurrentView();
        showView("send");
    });

    $("btn-address-token-receive").addEventListener("click", () => {
        ctx.showReceive();
    });
}

module.exports = { init, show };
