/**
 * Yield position tracking — queries on-chain aToken and vault share balances
 * for a given address across Aave V3 and Morpho Blue vaults.
 */

import type { YieldPosition, SupportedYieldChain, StablecoinSymbol } from "./yield-protocols";
import {
	AAVE_ATOKENS, MORPHO_VAULTS,
	STABLECOIN_ADDRESSES, STABLECOIN_DECIMALS,
	encodeBalanceOf, encodeConvertToAssets,
	YIELD_CHAIN_NAMES,
} from "./yield-protocols";
import { getRpcUrl } from "../mod";
import { getYieldRates } from "./yield-rates";

async function rpcCall(rpcUrl: string, to: string, data: string): Promise<string | null> {
	try {
		const res = await fetch(rpcUrl, {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify({
				jsonrpc: "2.0", id: 1,
				method: "eth_call",
				params: [{ to, data }, "latest"],
			}),
			signal: AbortSignal.timeout(5000),
		});
		const result = (await res.json()).result;
		if (!result || result === "0x" || result === "0x0") return null;
		return result;
	} catch {
		return null;
	}
}

interface PositionQuery {
	protocol: "aave-v3" | "morpho-blue";
	chainId: SupportedYieldChain;
	asset: StablecoinSymbol;
	assetAddress: string;
	vaultAddress: string;
	vaultName?: string;
}

function buildPositionQueries(): PositionQuery[] {
	const queries: PositionQuery[] = [];
	const chains: SupportedYieldChain[] = ["1", "8453"];

	for (const chainId of chains) {
		// Aave aTokens
		const aTokens = AAVE_ATOKENS[chainId];
		if (aTokens) {
			for (const [asset, aToken] of Object.entries(aTokens)) {
				const assetAddr = STABLECOIN_ADDRESSES[chainId]?.[asset as StablecoinSymbol];
				if (assetAddr && assetAddr !== "0x0000000000000000000000000000000000000000") {
					queries.push({
						protocol: "aave-v3",
						chainId,
						asset: asset as StablecoinSymbol,
						assetAddress: assetAddr,
						vaultAddress: aToken,
					});
				}
			}
		}

		// Morpho vaults
		for (const vault of MORPHO_VAULTS[chainId] || []) {
			const assetAddr = STABLECOIN_ADDRESSES[chainId]?.[vault.asset];
			if (assetAddr && assetAddr !== "0x0000000000000000000000000000000000000000") {
				queries.push({
					protocol: "morpho-blue",
					chainId,
					asset: vault.asset,
					assetAddress: assetAddr,
					vaultAddress: vault.address,
					vaultName: vault.name,
				});
			}
		}
	}

	return queries;
}

export async function getYieldPositions(address: string): Promise<YieldPosition[]> {
	const queries = buildPositionQueries();
	const positions: YieldPosition[] = [];

	// Get current rates for APY enrichment
	const rates = await getYieldRates();
	const rateMap = new Map(rates.map((r) => [`${r.protocol}:${r.chainId}:${r.vaultAddress.toLowerCase()}`, r]));

	const results = await Promise.allSettled(
		queries.map(async (q) => {
			const rpcUrl = getRpcUrl(q.chainId);
			if (!rpcUrl) return null;

			const balanceHex = await rpcCall(rpcUrl, q.vaultAddress, encodeBalanceOf(address));
			if (!balanceHex) return null;

			const shares = BigInt(balanceHex);
			if (shares === 0n) return null;

			const decimals = STABLECOIN_DECIMALS[q.asset];
			let underlying = shares; // For Aave, aToken balance = underlying (rebasing)

			if (q.protocol === "morpho-blue") {
				// Convert shares to underlying via convertToAssets
				const assetsHex = await rpcCall(rpcUrl, q.vaultAddress, encodeConvertToAssets(shares));
				if (assetsHex) {
					underlying = BigInt(assetsHex);
				}
			}

			// Look up APY
			const rateKey = `${q.protocol}:${q.chainId}:${q.vaultAddress.toLowerCase()}`;
			const rate = rateMap.get(rateKey);
			const apy = rate?.apy || 0;

			const underlyingUSD = Number(underlying) / 10 ** decimals; // stablecoins ≈ $1
			const annualEarnings = underlyingUSD * (apy / 100);
			const dailyEarnings = annualEarnings / 365;

			return {
				protocol: q.protocol,
				chainId: q.chainId,
				asset: q.asset,
				assetAddress: q.assetAddress,
				vaultAddress: q.vaultAddress,
				shares: shares.toString(),
				underlying: underlying.toString(),
				decimals,
				apy,
				dailyEarnings,
				annualEarnings,
			} satisfies YieldPosition;
		}),
	);

	for (const r of results) {
		if (r.status === "fulfilled" && r.value) {
			positions.push(r.value);
		}
	}

	// Sort by underlying value descending
	positions.sort((a, b) => {
		const aVal = Number(BigInt(b.underlying)) / 10 ** b.decimals;
		const bVal = Number(BigInt(a.underlying)) / 10 ** a.decimals;
		return aVal - bVal;
	});

	return positions;
}
