/**
 * Zerion DeFi positions — fetches protocol positions (Aave, Uniswap, etc.)
 * with 5-minute in-memory cache. Requires ZERION_API_KEY env var.
 */

export interface DefiPosition {
	protocol: string;
	type: string;       // "deposit", "loan", "staked", "locked", "reward"
	chain: string;      // chain name
	chainId: string;
	tokens: Array<{ symbol: string; amount: number; valueUSD: number }>;
	totalValueUSD: number;
}

interface CacheEntry {
	positions: DefiPosition[];
	ts: number;
}

const TTL = 5 * 60 * 1000;
const cache = new Map<string, CacheEntry>();
const inFlight = new Map<string, Promise<DefiPosition[]>>();

// Zerion chain ID → our chain ID mapping
const ZERION_CHAIN_MAP: Record<string, string> = {
	ethereum: "1",
	optimism: "10",
	"gnosis-chain": "100",
	"xdai": "100",
	polygon: "137",
	base: "8453",
	arbitrum: "42161",
	"binance-smart-chain": "56",
	avalanche: "43114",
	celo: "42220",
	"zksync-era": "324",
	linea: "59144",
};

function getApiKey(): string | null {
	return process.env.ZERION_API_KEY || null;
}

/**
 * Fetch DeFi protocol positions for an address via Zerion API.
 * Returns empty array if ZERION_API_KEY is not set.
 */
export async function getDefiPositions(address: string): Promise<DefiPosition[]> {
	const apiKey = getApiKey();
	if (!apiKey) return [];

	const lower = address.toLowerCase();

	// Check cache
	const cached = cache.get(lower);
	if (cached && Date.now() - cached.ts < TTL) return cached.positions;

	// Deduplicate concurrent requests
	const pending = inFlight.get(lower);
	if (pending) return pending;

	const promise = (async (): Promise<DefiPosition[]> => {
		try {
			const auth = btoa(`${apiKey}:`);
			const url = `https://api.zerion.io/v1/wallets/${lower}/positions/?filter[positions]=only_complex&currency=usd&filter[trash]=only_non_trash`;
			const res = await fetch(url, {
				headers: {
					accept: "application/json",
					authorization: `Basic ${auth}`,
				},
				signal: AbortSignal.timeout(15000),
			});

			if (res.status === 429) {
				console.warn("[defi-positions] Zerion rate limited");
				return [];
			}
			if (!res.ok) {
				console.warn(`[defi-positions] Zerion API error: ${res.status}`);
				return [];
			}

			const data = await res.json() as { data?: any[] };
			const positions = (data.data || []).map(normalizePosition).filter(Boolean) as DefiPosition[];

			cache.set(lower, { positions, ts: Date.now() });
			return positions;
		} catch (e) {
			console.warn("[defi-positions] Failed to fetch:", e);
			return [];
		} finally {
			inFlight.delete(lower);
		}
	})();

	inFlight.set(lower, promise);
	return promise;
}

function normalizePosition(item: any): DefiPosition | null {
	try {
		const attrs = item.attributes;
		if (!attrs) return null;

		const protocol = attrs.protocol_id || attrs.protocol || "Unknown";
		const posType = attrs.position_type || "deposit";
		const chainRaw = attrs.chain || item.relationships?.chain?.data?.id || "";
		const chainId = ZERION_CHAIN_MAP[chainRaw] || "";

		const tokens: DefiPosition["tokens"] = [];
		let totalValueUSD = 0;

		// Fungible positions — may have a single fungible_info or multiple
		if (attrs.fungible_info) {
			const fi = attrs.fungible_info;
			const amount = attrs.quantity?.float ?? 0;
			const value = attrs.value ?? 0;
			tokens.push({
				symbol: fi.symbol || "???",
				amount,
				valueUSD: value,
			});
			totalValueUSD += value;
		}

		// Interpretations — complex positions with sub-tokens
		if (attrs.interpretations) {
			for (const interp of attrs.interpretations) {
				if (interp.tokens) {
					for (const t of interp.tokens) {
						const tVal = t.value ?? 0;
						tokens.push({
							symbol: t.fungible_info?.symbol || t.symbol || "???",
							amount: t.quantity?.float ?? 0,
							valueUSD: tVal,
						});
						totalValueUSD += tVal;
					}
				}
			}
		}

		// Fall back to top-level value if tokens didn't capture it
		if (totalValueUSD === 0 && attrs.value) {
			totalValueUSD = attrs.value;
		}

		if (totalValueUSD < 0.01 && tokens.length === 0) return null;

		// Prettify protocol name
		const prettyProtocol = protocol
			.replace(/-/g, " ")
			.replace(/\b\w/g, (c: string) => c.toUpperCase());

		return {
			protocol: prettyProtocol,
			type: posType,
			chain: chainRaw.replace(/-/g, " ").replace(/\b\w/g, (c: string) => c.toUpperCase()),
			chainId,
			tokens,
			totalValueUSD,
		};
	} catch {
		return null;
	}
}
