import { z } from 'zod';
import type { PendingReward } from '@t2000/sdk';
import { defineTool } from '../v2/define-tool.js';
import { requireAgent } from './utils.js';

interface ClaimRewardsResult {
  success: boolean;
  tx: string | null;
  rewards: PendingReward[];
  totalValueUsd: number;
  gasCost: number;
  degraded: boolean;
  degradationReason: string | null;
}

/**
 * Format an amount with adaptive precision so a 0.0165 vSUI claim
 * doesn't get stringified as "0.02" and the LLM can narrate the
 * actual on-chain credit accurately.
 */
function formatAmount(amount: number): string {
  if (!Number.isFinite(amount) || amount <= 0) return '0';
  if (amount >= 1) return amount.toFixed(4).replace(/\.?0+$/, '');
  if (amount >= 0.0001) return amount.toFixed(6).replace(/\.?0+$/, '');
  return amount.toExponential(2);
}

export const claimRewardsTool = defineTool({
  name: 'claim_rewards',
  description:
    'Claim all pending protocol rewards across lending adapters. Returns the claimed reward breakdown (per-asset symbol + amount), total USD value (best effort — may be 0 when oracle prices are unavailable), and the on-chain tx hash. When the rewards list is empty the response will explicitly say "no pending rewards"; when it is non-empty narrate the per-symbol amounts even if totalValueUsd is 0 (the on-chain credit still happened). ' +
    'Payment Intent: composable — when paired with another composable write in the same request (e.g. "claim rewards and stake them"), emit all calls in the same assistant turn so the engine compiles them into one atomic Payment Intent the user signs once.',
  inputSchema: z.object({}),
  isReadOnly: false,
  permissionLevel: 'confirm',
  flags: { mutating: true },
  // claim_rewards has no inputs — preflight is structural (satisfies the
  // "every write tool MUST implement preflight" rule in
  // safeguards-defense-in-depth.mdc) and will receive any future inputs.
  preflight: () => ({ valid: true }),

  async call(_input, context) {
    const agent = requireAgent(context);
    let result;
    try {
      result = await agent.claimRewards();
    } catch (err) {
      // [S18-F20] NAVI rewards degradation surfaces as a T2000Error with
      // code 'PROTOCOL_UNAVAILABLE' (see `packages/sdk/src/protocols/navi.ts`).
      // Pre-fix this was silently swallowed and the LLM narrated "no
      // pending rewards" — a false negative that lied to the user about
      // their on-chain state. Now we surface degradation truthfully.
      const errAny = err as { code?: string; message?: string };
      const isProtocolDown = errAny?.code === 'PROTOCOL_UNAVAILABLE';
      const detail =
        typeof errAny?.message === 'string' ? errAny.message.replace(/^[^:]*:\s*/, '') : '';
      const displayText = isProtocolDown
        ? `Could not check pending rewards — NAVI is degraded right now${detail ? ` (${detail.slice(0, 80)})` : ''}. Try again in a moment.`
        : 'Could not check pending rewards — protocol error. Try again in a moment.';
      const data: ClaimRewardsResult = {
        success: false,
        tx: null,
        rewards: [],
        totalValueUsd: 0,
        gasCost: 0,
        degraded: true,
        degradationReason: errAny?.code ?? 'UNKNOWN',
      };
      return { data, displayText };
    }

    // The SDK adapter doesn't have access to a price oracle, so
    // `estimatedValueUsd` is always 0 from upstream. The engine, however,
    // has a `priceCache` populated by the harness (symbol → USD), so we
    // enrich here. This means the card / LLM can report "$0.04 vSUI"
    // instead of "$0.00" whenever a price is known, while still gracefully
    // degrading to per-symbol amounts when it isn't.
    const priceCache = context.priceCache;
    const enrichedRewards = result.rewards.map((r) => {
      if (r.estimatedValueUsd > 0) return r;
      const price = priceCache?.get(r.symbol.toUpperCase());
      if (!price || !Number.isFinite(price) || price <= 0) return r;
      return { ...r, estimatedValueUsd: r.amount * price };
    });

    const totalValueUsd = enrichedRewards.reduce(
      (s, r) => s + (Number.isFinite(r.estimatedValueUsd) ? r.estimatedValueUsd : 0),
      0,
    );

    const txShort = result.tx ? `${result.tx.slice(0, 8)}…` : '';
    let displayText: string;

    if (enrichedRewards.length === 0) {
      displayText = 'No pending rewards to claim.';
    } else {
      // Always include per-symbol amounts so the narration is grounded
      // in the actual on-chain credit even when USD pricing is missing.
      const breakdown = enrichedRewards
        .map((r) => `${formatAmount(r.amount)} ${r.symbol}`)
        .join(', ');
      const usdSuffix = totalValueUsd > 0 ? ` (~$${totalValueUsd.toFixed(2)})` : '';
      const txSuffix = txShort ? ` (tx: ${txShort})` : '';
      displayText = `Claimed ${breakdown}${usdSuffix}${txSuffix}`;
    }

    const data: ClaimRewardsResult = {
      success: result.success,
      tx: result.tx || null,
      rewards: enrichedRewards,
      totalValueUsd,
      gasCost: result.gasCost,
      degraded: false,
      degradationReason: null,
    };
    return { data, displayText };
  },
});
