/**
 * Yield strategy engine — advisory recommendations for idle stablecoin deployment.
 * Detects idle assets, suggests allocations, and identifies rebalance opportunities.
 */

import type {
	YieldOpportunity, YieldPosition, YieldSuggestion,
	SupportedYieldChain, StablecoinSymbol,
} from "./yield-protocols";
import { STABLECOIN_ADDRESSES, STABLECOIN_DECIMALS, YIELD_CHAIN_NAMES } from "./yield-protocols";
import { getYieldRates } from "./yield-rates";
import { getYieldPositions } from "./yield-positions";
import { getRpcUrl } from "../mod";

// ── Strategy config ──
const CONFIG = {
	minIdleThresholdUSD: 1000,   // Don't suggest deposits below $1000
	maxProtocolAllocation: 0.7,  // Max 70% in one protocol
	minAPY: 1.0,                 // Skip opportunities below 1% APY
	minTVL: 10_000_000,          // Skip pools with < $10M TVL
	gasCostRatioCap: 0.1,        // Don't suggest if gas > 10% of annual yield
	rebalanceThreshold: 2.0,     // Only rebalance if APY diff > 2%
};

// ── Gas price estimation ──
const GAS_PRICES_GWEI: Record<SupportedYieldChain, number> = {
	"1": 30,   // ~30 gwei on Ethereum
	"8453": 0.01, // ~0.01 gwei on Base
};

const ETH_PRICE_USD = 3500; // Approximate, could fetch live
const DEPOSIT_GAS = 250_000; // Approve + supply/deposit via MultiSend
const WITHDRAW_GAS = 150_000;

function estimateGasCostUSD(chainId: SupportedYieldChain, gasUnits: number): number {
	const gweiPrice = GAS_PRICES_GWEI[chainId] || 30;
	const ethCost = (gweiPrice * gasUnits) / 1e9;
	return ethCost * ETH_PRICE_USD;
}

// ── Idle stablecoin detection ──

interface IdleBalance {
	chainId: SupportedYieldChain;
	asset: StablecoinSymbol;
	assetAddress: string;
	balance: string;
	balanceUSD: number;
}

async function rpcBalanceOf(rpcUrl: string, token: string, address: string): Promise<bigint> {
	const paddedAddr = address.slice(2).toLowerCase().padStart(64, "0");
	const data = `0x70a08231${paddedAddr}`;
	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: token, data }, "latest"],
			}),
			signal: AbortSignal.timeout(5000),
		});
		const result = (await res.json()).result;
		if (!result || result === "0x" || result === "0x0") return 0n;
		return BigInt(result);
	} catch {
		return 0n;
	}
}

export async function detectIdleStablecoins(address: string): Promise<IdleBalance[]> {
	const chains: SupportedYieldChain[] = ["1", "8453"];
	const assets: StablecoinSymbol[] = ["USDC", "USDT", "DAI"];
	const idle: IdleBalance[] = [];

	const queries = chains.flatMap((chainId) =>
		assets.map((asset) => ({ chainId, asset })),
	);

	await Promise.allSettled(
		queries.map(async ({ chainId, asset }) => {
			const assetAddr = STABLECOIN_ADDRESSES[chainId]?.[asset];
			if (!assetAddr || assetAddr === "0x0000000000000000000000000000000000000000") return;

			const rpcUrl = getRpcUrl(chainId);
			if (!rpcUrl) return;

			const balance = await rpcBalanceOf(rpcUrl, assetAddr, address);
			if (balance === 0n) return;

			const decimals = STABLECOIN_DECIMALS[asset];
			const balanceUSD = Number(balance) / 10 ** decimals;

			if (balanceUSD >= CONFIG.minIdleThresholdUSD) {
				idle.push({
					chainId,
					asset,
					assetAddress: assetAddr,
					balance: balance.toString(),
					balanceUSD,
				});
			}
		}),
	);

	idle.sort((a, b) => b.balanceUSD - a.balanceUSD);
	return idle;
}

// ── Strategy computation ──

export interface StrategyResult {
	idleBalances: IdleBalance[];
	positions: YieldPosition[];
	suggestions: YieldSuggestion[];
	totalIdleUSD: number;
	totalDepositedUSD: number;
	weightedAPY: number;
}

export async function computeStrategy(address: string): Promise<StrategyResult> {
	// Fetch everything in parallel
	const [idle, positions, rates] = await Promise.all([
		detectIdleStablecoins(address),
		getYieldPositions(address),
		getYieldRates(),
	]);

	const totalIdleUSD = idle.reduce((sum, b) => sum + b.balanceUSD, 0);
	const totalDepositedUSD = positions.reduce((sum, p) => {
		return sum + Number(BigInt(p.underlying)) / 10 ** p.decimals;
	}, 0);

	// Weighted APY across positions
	let weightedAPY = 0;
	if (totalDepositedUSD > 0) {
		const weighted = positions.reduce((sum, p) => {
			const usd = Number(BigInt(p.underlying)) / 10 ** p.decimals;
			return sum + usd * p.apy;
		}, 0);
		weightedAPY = weighted / totalDepositedUSD;
	}

	const suggestions: YieldSuggestion[] = [];

	// Filter eligible opportunities
	const eligibleRates = rates.filter(
		(r) => r.apy >= CONFIG.minAPY && (!r.tvl || r.tvl >= CONFIG.minTVL),
	);

	// ── Deposit suggestions (idle → yield) ──
	for (const idleBalance of idle) {
		// Find best opportunity on same chain + asset
		const sameChainRates = eligibleRates.filter(
			(r) => r.chainId === idleBalance.chainId && r.asset === idleBalance.asset,
		);

		// Also consider cross-chain if on Ethereum (high gas) — prefer Base
		const bestOnChain = sameChainRates[0]; // Already sorted by APY
		if (!bestOnChain) continue;

		const gasCost = estimateGasCostUSD(idleBalance.chainId, DEPOSIT_GAS);
		const annualYield = idleBalance.balanceUSD * (bestOnChain.apy / 100);

		// Skip if gas cost > configured ratio of annual yield
		if (gasCost > annualYield * CONFIG.gasCostRatioCap) continue;

		const priority = idleBalance.balanceUSD > 50_000 ? "high"
			: idleBalance.balanceUSD > 10_000 ? "medium"
			: "low";

		suggestions.push({
			type: "deposit",
			priority,
			to: bestOnChain,
			amount: idleBalance.balance,
			amountUSD: idleBalance.balanceUSD,
			reason: `${idleBalance.balanceUSD.toLocaleString("en-US", { style: "currency", currency: "USD" })} ${idleBalance.asset} idle on ${YIELD_CHAIN_NAMES[idleBalance.chainId]} — earn ${bestOnChain.apy.toFixed(2)}% APY`,
			estimatedGasCostUSD: gasCost,
		});
	}

	// ── Rebalance suggestions (low APY → high APY) ──
	for (const pos of positions) {
		const posUSD = Number(BigInt(pos.underlying)) / 10 ** pos.decimals;
		if (posUSD < CONFIG.minIdleThresholdUSD) continue;

		// Find better opportunity for same asset (any chain)
		const betterOptions = eligibleRates.filter(
			(r) =>
				r.asset === pos.asset &&
				r.apy > pos.apy + CONFIG.rebalanceThreshold &&
				// Don't suggest moving to the same vault
				r.vaultAddress.toLowerCase() !== pos.vaultAddress.toLowerCase(),
		);

		if (betterOptions.length === 0) continue;
		const best = betterOptions[0];

		const withdrawGas = estimateGasCostUSD(pos.chainId, WITHDRAW_GAS);
		const depositGas = estimateGasCostUSD(best.chainId, DEPOSIT_GAS);
		const totalGas = withdrawGas + depositGas;
		const apyGain = best.apy - pos.apy;
		const annualGain = posUSD * (apyGain / 100);

		if (totalGas > annualGain * CONFIG.gasCostRatioCap) continue;

		suggestions.push({
			type: "rebalance",
			priority: apyGain > 5 ? "high" : "medium",
			from: {
				protocol: pos.protocol,
				chainId: pos.chainId,
				vaultAddress: pos.vaultAddress,
				apy: pos.apy,
			},
			to: best,
			amount: pos.underlying,
			amountUSD: posUSD,
			reason: `Move ${posUSD.toLocaleString("en-US", { style: "currency", currency: "USD" })} ${pos.asset} from ${pos.protocol} (${pos.apy.toFixed(2)}%) to ${best.protocol} (${best.apy.toFixed(2)}%) — +${apyGain.toFixed(2)}% APY`,
			estimatedGasCostUSD: totalGas,
		});
	}

	// Sort: high priority first, then by amountUSD
	suggestions.sort((a, b) => {
		const prio = { high: 0, medium: 1, low: 2 };
		const prioDiff = prio[a.priority] - prio[b.priority];
		if (prioDiff !== 0) return prioDiff;
		return b.amountUSD - a.amountUSD;
	});

	return {
		idleBalances: idle,
		positions,
		suggestions,
		totalIdleUSD,
		totalDepositedUSD,
		weightedAPY,
	};
}
