// Domain-based phishing detection using a vendored blocklist with delta updates.
//
// A community-maintained phishing domain blocklist is vendored in
// phishingBlocklist.json and bundled at build time. At runtime, we fetch
// the live list periodically and keep only the delta (new entries not in
// the vendored list) in memory. This keeps runtime memory usage small.
//
// The domain-checker checks the in-memory delta first (fresh/recent scam
// sites), then falls back to the vendored list.
//
// If the delta is under 256 KiB it is persisted to localStorage so it
// survives extension/service-worker restarts.

const vendoredConfig = require("./phishingBlocklist.json");

const BLOCKLIST_URL =
    "https://raw.githubusercontent.com/MetaMask/eth-phishing-detect/main/src/config.json";

const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
const REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
const DELTA_STORAGE_KEY = "phishing-delta";
const MAX_DELTA_BYTES = 256 * 1024; // 256 KiB

// Vendored set — built once from the bundled JSON.
const vendoredBlacklist = new Set(
    (vendoredConfig.blacklist || []).map((d) => d.toLowerCase()),
);

// Delta set — only entries from live list that are NOT in vendored.
let deltaBlacklist = new Set();
let lastFetchTime = 0;
let fetchPromise = null;
let refreshTimer = null;

/**
 * Load delta entries from localStorage on startup.
 * Called once during module initialization in the background script.
 */
function loadDeltaFromStorage() {
    try {
        const raw = localStorage.getItem(DELTA_STORAGE_KEY);
        if (!raw) return;
        const data = JSON.parse(raw);
        if (data.blacklist && Array.isArray(data.blacklist)) {
            deltaBlacklist = new Set(
                data.blacklist.map((d) => d.toLowerCase()),
            );
        }
    } catch {
        // localStorage unavailable or corrupt — start empty
    }
}

/**
 * Persist delta to localStorage if it fits within MAX_DELTA_BYTES.
 */
function saveDeltaToStorage() {
    try {
        const data = {
            blacklist: Array.from(deltaBlacklist),
        };
        const json = JSON.stringify(data);
        if (json.length < MAX_DELTA_BYTES) {
            localStorage.setItem(DELTA_STORAGE_KEY, json);
        } else {
            // Too large — remove stale key if present
            localStorage.removeItem(DELTA_STORAGE_KEY);
        }
    } catch {
        // localStorage unavailable — skip silently
    }
}

/**
 * Load a pre-parsed config and compute the delta against the vendored list.
 * Used for both live fetches and testing.
 *
 * @param {{ blacklist?: string[] }} config
 */
function loadConfig(config) {
    const liveBlacklist = (config.blacklist || []).map((d) => d.toLowerCase());

    // Delta = entries in the live list that are NOT in the vendored list
    deltaBlacklist = new Set(
        liveBlacklist.filter((d) => !vendoredBlacklist.has(d)),
    );

    lastFetchTime = Date.now();
    saveDeltaToStorage();
}

/**
 * Generate hostname variants for subdomain matching.
 * "sub.evil.com" yields ["sub.evil.com", "evil.com"].
 *
 * @param {string} hostname
 * @returns {string[]}
 */
function hostnameVariants(hostname) {
    const h = hostname.toLowerCase();
    const variants = [h];
    const parts = h.split(".");
    // Parent domains: a.b.c.d -> b.c.d, c.d
    for (let i = 1; i < parts.length - 1; i++) {
        variants.push(parts.slice(i).join("."));
    }
    return variants;
}

/**
 * Check if a hostname is on the phishing blocklist.
 * Checks delta first (fresh/recent scam sites), then vendored list.
 *
 * @param {string} hostname - The hostname to check.
 * @returns {boolean}
 */
function isPhishingDomain(hostname) {
    if (!hostname) return false;
    const variants = hostnameVariants(hostname);

    // Check delta blacklist first (fresh/recent scam sites), then vendored
    for (const v of variants) {
        if (deltaBlacklist.has(v) || vendoredBlacklist.has(v)) return true;
    }
    return false;
}

/**
 * Fetch the latest blocklist and compute delta against vendored data.
 * De-duplicates concurrent fetches. Results are cached for CACHE_TTL_MS.
 *
 * @returns {Promise<void>}
 */
async function updatePhishingList() {
    // Skip if recently fetched
    if (Date.now() - lastFetchTime < CACHE_TTL_MS && lastFetchTime > 0) {
        return;
    }

    // De-duplicate concurrent calls
    if (fetchPromise) return fetchPromise;

    fetchPromise = (async () => {
        try {
            const resp = await fetch(BLOCKLIST_URL);
            if (!resp.ok) throw new Error("HTTP " + resp.status);
            const config = await resp.json();
            loadConfig(config);
        } catch {
            // Silently fail — vendored list still provides coverage.
            // We'll retry next time.
        } finally {
            fetchPromise = null;
        }
    })();

    return fetchPromise;
}

/**
 * Start periodic refresh of the phishing list.
 * Should be called once from the background script on startup.
 */
function startPeriodicRefresh() {
    if (refreshTimer) return;
    refreshTimer = setInterval(updatePhishingList, REFRESH_INTERVAL_MS);
}

/**
 * Return the total blocklist size (vendored + delta) for diagnostics.
 *
 * @returns {number}
 */
function getBlocklistSize() {
    return vendoredBlacklist.size + deltaBlacklist.size;
}

/**
 * Return the delta blocklist size for diagnostics.
 *
 * @returns {number}
 */
function getDeltaSize() {
    return deltaBlacklist.size;
}

/**
 * Reset internal state (for testing).
 */
function _reset() {
    deltaBlacklist = new Set();
    lastFetchTime = 0;
    fetchPromise = null;
    if (refreshTimer) {
        clearInterval(refreshTimer);
        refreshTimer = null;
    }
}

// Load persisted delta on module initialization
loadDeltaFromStorage();

module.exports = {
    isPhishingDomain,
    updatePhishingList,
    startPeriodicRefresh,
    loadConfig,
    getBlocklistSize,
    getDeltaSize,
    hostnameVariants,
    _reset,
    // Exposed for testing only
    _getVendoredBlacklistSize: () => vendoredBlacklist.size,
    _getDeltaBlacklist: () => deltaBlacklist,
};
