import assert from 'node:assert/strict';
import {
    buildFmpCacheKey,
    clearFmpCacheForTests,
    fetchFmpJson,
    FMP_CACHE_TTL_MS,
    FmpFetchError,
    redactFmpUrlForLogs,
    setFmpCacheNowForTests,
} from './src/services/fmpCache.js';

type MockResponse = {
    ok: boolean;
    status: number;
    json: () => Promise<unknown>;
};

function createFetch(payloads: unknown[]) {
    let calls = 0;
    const fetchJson = async (): Promise<MockResponse> => {
        const payload = payloads[calls] ?? payloads[payloads.length - 1];
        calls += 1;
        return {
            ok: true,
            status: 200,
            json: async () => payload,
        };
    };

    return {
        fetchJson,
        get calls() {
            return calls;
        },
    };
}

async function main() {
    clearFmpCacheForTests();
    let now = 1_000;
    setFmpCacheNowForTests(() => now);

    const url = 'https://financialmodelingprep.com/api/v3/profile/AAPL?apikey=test';
    const otherUrl = 'https://financialmodelingprep.com/api/v3/profile/MSFT?apikey=test';
    const fetcher = createFetch([{ symbol: 'AAPL' }, { symbol: 'AAPL-refresh' }]);

    const cacheKey = buildFmpCacheKey(url);
    assert.notEqual(cacheKey, url, 'cache keys should not retain raw FMP URLs with query-string API keys');
    assert(!cacheKey.includes('apikey'), 'cache keys should not expose the FMP apikey parameter');
    assert.equal(
        redactFmpUrlForLogs(url),
        'https://financialmodelingprep.com/api/v3/profile/AAPL?apikey=%5BREDACTED%5D',
        'log-safe FMP URLs should redact apikey values',
    );

    assert.deepEqual(await fetchFmpJson(url, fetcher.fetchJson), { symbol: 'AAPL' });
    assert.deepEqual(await fetchFmpJson(url, fetcher.fetchJson), { symbol: 'AAPL' });
    assert.equal(fetcher.calls, 1, 'same full URL should be served from cache before TTL expiry');

    assert.deepEqual(await fetchFmpJson(otherUrl, fetcher.fetchJson), { symbol: 'AAPL-refresh' });
    assert.equal(fetcher.calls, 2, 'different full URL should get its own cache entry');

    now += FMP_CACHE_TTL_MS + 1;
    assert.deepEqual(await fetchFmpJson(url, fetcher.fetchJson), { symbol: 'AAPL-refresh' });
    assert.equal(fetcher.calls, 3, 'expired cache entry should be refetched');

    clearFmpCacheForTests();
    let failingCalls = 0;
    const failingFetch = async (): Promise<MockResponse> => {
        failingCalls += 1;
        return {
            ok: false,
            status: 429,
            json: async () => ({ error: 'rate limit' }),
        };
    };

    await assert.rejects(
        () => fetchFmpJson(url, failingFetch),
        (error) => error instanceof FmpFetchError && error.status === 429,
    );
    await assert.rejects(() => fetchFmpJson(url, failingFetch));
    assert.equal(failingCalls, 2, 'failed upstream responses should not be cached');

    clearFmpCacheForTests();
    console.log('FMP cache tests passed');
}

main().catch((error) => {
    console.error(error);
    process.exit(1);
});
