import { createHash } from 'node:crypto';

export const FMP_CACHE_TTL_MS = 30 * 60 * 1000;

export class FmpFetchError extends Error {
    constructor(public readonly status: number) {
        super(`FMP fetch failed with status ${status}`);
        this.name = 'FmpFetchError';
    }
}

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

interface FmpCacheEntry {
    expiresAt: number;
    data: unknown;
}

const fmpCache = new Map<string, FmpCacheEntry>();
const inFlightFmpRequests = new Map<string, Promise<unknown>>();

let now = () => Date.now();

export function redactFmpUrlForLogs(url: string): string {
    try {
        const parsed = new URL(url);
        if (parsed.searchParams.has('apikey')) {
            parsed.searchParams.set('apikey', '[REDACTED]');
        }
        return parsed.toString();
    } catch {
        return url.replace(/([?&]apikey=)[^&]+/i, '$1[REDACTED]');
    }
}

export function buildFmpCacheKey(url: string): string {
    return createHash('sha256').update(url).digest('hex');
}

export async function fetchFmpJson(url: string, fetchJson: FetchJson = fetch): Promise<unknown> {
    const cacheKey = buildFmpCacheKey(url);
    const cached = fmpCache.get(cacheKey);
    const currentTime = now();

    if (cached && cached.expiresAt > currentTime) {
        return cached.data;
    }

    if (cached) {
        fmpCache.delete(cacheKey);
    }

    const inFlight = inFlightFmpRequests.get(cacheKey);
    if (inFlight) {
        return inFlight;
    }

    const request = fetchJson(url)
        .then(async (response) => {
            if (!response.ok) {
                throw new FmpFetchError(response.status);
            }

            const data = await response.json();
            fmpCache.set(cacheKey, {
                data,
                expiresAt: now() + FMP_CACHE_TTL_MS,
            });
            return data;
        })
        .finally(() => {
            inFlightFmpRequests.delete(cacheKey);
        });

    inFlightFmpRequests.set(cacheKey, request);
    return request;
}

export function clearFmpCacheForTests() {
    fmpCache.clear();
    inFlightFmpRequests.clear();
    now = () => Date.now();
}

export function setFmpCacheNowForTests(nowProvider: () => number) {
    now = nowProvider;
}
