import {
  AlchemyWebhookHandlerResult,
  AlchemyAddressActivity,
  AlchemyWebhookPayload,
  DepositEvent,
  WalletMappingInput,
} from "../types";
import { parseChainStringMap } from "./ChainConfig";
import {
  normalizeEvmAmount,
  normalizeSolanaNativeAmount,
  normalizeSolanaTokenAmount,
} from "./AssetAmountNormalizer";
import { IMappingStore } from "./StoreContracts";

interface NetworkResolution {
  chainType: string | null;
}

interface SolanaTokenBalanceEntry {
  accountIndex: number;
  mint: string;
  owner: string;
  amount: string;
  decimals: number | null;
}

const DEFAULT_SOLANA_MINT_SYMBOLS: Record<string, string> = {
  EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: "USDC",
  Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: "USDT",
  CXk2AMBfi3TwaEL2468s6zP8xq9NxTXjp9gjMgzeUynM: "USDC",
  So11111111111111111111111111111111111111112: "SOL",
};

export class AlchemyWebhookNormalizationService {
  private readonly supportedCategories = new Set(["external", "internal", "token", "erc20"]);
  private readonly solanaMintSymbols: Record<string, string>;
  private readonly feeFundingAddressByChainType: Record<string, string>;

  constructor(
    private mappingStoreService: IMappingStore,
    solanaMintSymbolsRaw?: string,
    feeFundingAddressesRaw?: string
  ) {
    this.solanaMintSymbols = {
      ...DEFAULT_SOLANA_MINT_SYMBOLS,
      ...this.parseMintSymbolMap(solanaMintSymbolsRaw),
    };
    this.feeFundingAddressByChainType = parseChainStringMap(
      feeFundingAddressesRaw,
      "SWEEPER_FEE_FUNDING_ADDRESSES"
    );
  }

  async normalize(payload: AlchemyWebhookPayload): Promise<{
    events: DepositEvent[];
    metrics: Omit<AlchemyWebhookHandlerResult, "accepted" | "duplicates">;
  }> {
    if (payload.type !== "ADDRESS_ACTIVITY") {
      return {
        events: [],
        metrics: {
          ignored: 1,
          unsupported: 0,
          reorgEvents: 0,
        },
      };
    }

    const activityItems = Array.isArray(payload.event?.activity) ? payload.event?.activity || [] : [];
    const transactionItems = Array.isArray(payload.event?.transaction)
      ? payload.event?.transaction || []
      : [];
    const resolvedNetwork = this.resolveNetwork(payload.event?.network);

    if (activityItems.length === 0 && transactionItems.length > 0) {
      return this.normalizeSolanaTransactions(payload, transactionItems, resolvedNetwork);
    }

    const events: DepositEvent[] = [];
    let ignored = 0;
    let unsupported = 0;
    let reorgEvents = 0;

    for (const activity of activityItems) {
      const result = await this.normalizeActivity(payload, activity, resolvedNetwork);
      if (result.reason === "ignored") {
        ignored += 1;
        continue;
      }

      if (result.reason === "unsupported") {
        unsupported += 1;
        continue;
      }

      if (result.reason === "reorg") {
        reorgEvents += 1;
        continue;
      }

      events.push(result.event);
    }

    return {
      events,
      metrics: {
        ignored,
        unsupported,
        reorgEvents,
      },
    };
  }

  private async normalizeSolanaTransactions(
    payload: AlchemyWebhookPayload,
    transactions: Array<Record<string, unknown>>,
    resolvedNetwork: NetworkResolution
  ): Promise<{
    events: DepositEvent[];
    metrics: Omit<AlchemyWebhookHandlerResult, "accepted" | "duplicates">;
  }> {
    const events: DepositEvent[] = [];
    let ignored = 0;
    let unsupported = 0;
    const reorgEvents = 0;
    const fallbackSlot = payload.event?.slot ?? null;

    for (const [transactionIndex, transaction] of transactions.entries()) {
      const txHash = this.readString(transaction, ["signature", "transactionHash", "hash"]);
      const blockNumber = this.readNumber(transaction, ["slot"]) ?? fallbackSlot;
      const nativeTransfers = this.readObjectArray(transaction.nativeTransfers);
      const tokenTransfers = this.readObjectArray(transaction.tokenTransfers);

      if (!txHash) {
        ignored += 1;
        continue;
      }

      if (nativeTransfers.length === 0 && tokenTransfers.length === 0) {
        const tokenBalanceEvents = await this.normalizeSolanaTokenBalanceChanges(
          payload,
          transaction,
          txHash,
          blockNumber,
          resolvedNetwork,
          transactionIndex
        );
        if (tokenBalanceEvents.length > 0) {
          events.push(...tokenBalanceEvents);
          continue;
        }

        const balanceDerivedEvents = await this.normalizeSolanaBalanceChanges(
          payload,
          transaction,
          txHash,
          blockNumber,
          resolvedNetwork,
          transactionIndex
        );

        if (balanceDerivedEvents.length === 0) {
          ignored += 1;
          continue;
        }

        events.push(...balanceDerivedEvents);
        continue;
      }

      for (const [transferIndex, transfer] of nativeTransfers.entries()) {
        const event = await this.buildSolanaDepositEvent({
          payload,
          transfer,
          txHash,
          blockNumber,
          resolvedNetwork,
          transferIndex: `${transactionIndex}:native:${transferIndex}`,
          asset: "SOL",
          assetAddress: null,
          recipientKeys: ["toUserAccount", "to", "destination", "toAddress"],
          senderKeys: ["fromUserAccount", "from", "source", "fromAddress"],
          amountKeys: ["amount", "lamports", "value"],
        });

        if (event) {
          events.push(event);
        } else {
          ignored += 1;
        }
      }

      for (const [transferIndex, transfer] of tokenTransfers.entries()) {
        const tokenAmount = this.readTokenAmount(transfer);
        if (!tokenAmount) {
          ignored += 1;
          continue;
        }

        const event = await this.buildSolanaDepositEvent({
          payload,
          transfer,
          txHash,
          blockNumber,
          resolvedNetwork,
          transferIndex: `${transactionIndex}:token:${transferIndex}`,
          asset:
            this.readString(transfer, ["symbol", "asset", "tokenSymbol", "mint"]) || "SPL_TOKEN",
          assetAddress: this.readString(transfer, ["mint", "tokenAddress", "assetAddress"]),
          recipientKeys: [
            "toUserAccount",
            "toOwner",
            "to",
            "destination",
            "toAddress",
            "toTokenAccount",
          ],
          senderKeys: [
            "fromUserAccount",
            "fromOwner",
            "from",
            "source",
            "fromAddress",
            "fromTokenAccount",
          ],
          amountOverride: tokenAmount,
        });

        if (event) {
          events.push(event);
        } else {
          ignored += 1;
        }
      }
    }

    return {
      events,
      metrics: {
        ignored,
        unsupported,
        reorgEvents,
      },
    };
  }

  private async normalizeSolanaTokenBalanceChanges(
    payload: AlchemyWebhookPayload,
    transaction: Record<string, unknown>,
    txHash: string,
    blockNumber: number | null,
    resolvedNetwork: NetworkResolution,
    transactionIndex: number
  ): Promise<DepositEvent[]> {
    const metaEntries = this.readObjectArray(transaction.meta);
    const meta = metaEntries[0];
    if (!meta) {
      return [];
    }

    const preTokenBalances = this.readSolanaTokenBalances(meta.pre_token_balances);
    const postTokenBalances = this.readSolanaTokenBalances(meta.post_token_balances);
    if (preTokenBalances.length === 0 && postTokenBalances.length === 0) {
      return [];
    }

    const byKey = new Map<string, { pre?: SolanaTokenBalanceEntry; post?: SolanaTokenBalanceEntry }>();

    for (const balance of preTokenBalances) {
      const key = this.solanaTokenBalanceKey(balance);
      const current = byKey.get(key) || {};
      current.pre = balance;
      byKey.set(key, current);
    }

    for (const balance of postTokenBalances) {
      const key = this.solanaTokenBalanceKey(balance);
      const current = byKey.get(key) || {};
      current.post = balance;
      byKey.set(key, current);
    }

    const negativeByMint = new Map<string, Array<{ owner: string; delta: bigint }>>();
    for (const { pre, post } of byKey.values()) {
      const mint = post?.mint || pre?.mint;
      const owner = post?.owner || pre?.owner;
      if (!mint || !owner) {
        continue;
      }

      const preAmount = BigInt(pre?.amount || "0");
      const postAmount = BigInt(post?.amount || "0");
      const delta = postAmount - preAmount;
      if (delta >= 0n) {
        continue;
      }

      const current = negativeByMint.get(mint) || [];
      current.push({ owner, delta });
      negativeByMint.set(mint, current);
    }

    const events: DepositEvent[] = [];
    let positiveIndex = 0;

    for (const { pre, post } of byKey.values()) {
      const target = post || pre;
      if (!target?.mint || !target.owner) {
        continue;
      }

      const preAmount = BigInt(pre?.amount || "0");
      const postAmount = BigInt(post?.amount || "0");
      const delta = postAmount - preAmount;
      if (delta <= 0n) {
        continue;
      }

      const mapping = await this.mappingStoreService.getByTrackedAddress(target.owner);
      if (!mapping) {
        continue;
      }

      const sender = negativeByMint
        .get(target.mint)
        ?.sort((left, right) => (left.delta < right.delta ? -1 : left.delta > right.delta ? 1 : 0))[0]
        ?.owner;

      const uniqueSuffix = `${transactionIndex}:token-balance:${positiveIndex}`;
      positiveIndex += 1;

      events.push({
        source: "alchemy",
        status: "confirmed",
        idempotency_key: `${txHash}:${uniqueSuffix}`,
        user_id: mapping.userId,
        wallet_address: target.owner,
        amount: normalizeSolanaTokenAmount(delta.toString(10), target.decimals),
        asset: this.resolveSolanaAssetSymbol(target.mint),
        tx_hash: txHash,
        block_number: blockNumber,
        log_index: uniqueSuffix,
        from_address: sender || null,
        to_address: target.owner,
        asset_address: target.mint,
        chain_type: mapping.wallet.chain_type || resolvedNetwork.chainType,
        occurred_at: payload.createdAt || new Date().toISOString(),
        raw_payload: {
          webhook: payload,
          transaction,
          token_balance: target,
          derived_from: "token_balances",
        },
      });
    }

    return events.filter((event) => !this.isInternalFeeFundingEvent(event));
  }

  private async normalizeSolanaBalanceChanges(
    payload: AlchemyWebhookPayload,
    transaction: Record<string, unknown>,
    txHash: string,
    blockNumber: number | null,
    resolvedNetwork: NetworkResolution,
    transactionIndex: number
  ): Promise<DepositEvent[]> {
    const transactionEntries = this.readObjectArray(transaction.transaction);
    const metaEntries = this.readObjectArray(transaction.meta);
    const accountKeys = this.extractSolanaAccountKeys(transactionEntries);
    const meta = metaEntries[0];

    if (!meta || accountKeys.length === 0) {
      return [];
    }

    const preBalances = this.readNumberArray(meta.pre_balances);
    const postBalances = this.readNumberArray(meta.post_balances);
    if (preBalances.length === 0 || preBalances.length !== postBalances.length) {
      return [];
    }

    const deltas = accountKeys.map((address, index) => ({
      address,
      delta: (postBalances[index] ?? 0) - (preBalances[index] ?? 0),
    }));

    const sender = deltas
      .filter((entry) => entry.delta < 0)
      .sort((left, right) => left.delta - right.delta)[0]?.address || null;

    const events: DepositEvent[] = [];

    for (const [balanceIndex, entry] of deltas.entries()) {
      if (entry.delta <= 0) {
        continue;
      }

      const mapping = await this.mappingStoreService.getByTrackedAddress(entry.address);
      if (!mapping) {
        continue;
      }

      const uniqueSuffix = `${transactionIndex}:balance:${balanceIndex}`;
      events.push({
        source: "alchemy",
        status: "confirmed",
        idempotency_key: `${txHash}:${uniqueSuffix}`,
        user_id: mapping.userId,
        wallet_address: entry.address,
        amount: normalizeSolanaNativeAmount(entry.delta),
        asset: "SOL",
        tx_hash: txHash,
        block_number: blockNumber,
        log_index: uniqueSuffix,
        from_address: sender,
        to_address: entry.address,
        asset_address: null,
        chain_type: mapping.wallet.chain_type || resolvedNetwork.chainType,
        occurred_at: payload.createdAt || new Date().toISOString(),
        raw_payload: {
          webhook: payload,
          transaction,
          derived_from: "balance_changes",
        },
      });
    }

    return events.filter((event) => !this.isInternalFeeFundingEvent(event));
  }

  private async normalizeActivity(
    payload: AlchemyWebhookPayload,
    activity: AlchemyAddressActivity,
    resolvedNetwork: NetworkResolution
  ): Promise<
    | { reason: "ignored" }
    | { reason: "unsupported" }
    | { reason: "reorg" }
    | { reason: "accepted"; event: DepositEvent }
  > {
    const recipient = activity.toAddress?.trim();
    if (!recipient) {
      return { reason: "ignored" };
    }

    if (!this.isSupportedActivity(activity)) {
      return { reason: "unsupported" };
    }

    const mapping = await this.mappingStoreService.getByTrackedAddress(recipient);
    if (!mapping) {
      return { reason: "ignored" };
    }

    const removed = Boolean(activity.log?.removed);
    const transactionHash = activity.hash || activity.log?.transactionHash || null;
    if (!transactionHash) {
      return { reason: "ignored" };
    }

    const blockNumber = this.hexToNumber(activity.blockNum || activity.log?.blockNumber || null);
    const amount = this.resolveAmount(activity);
    if (!amount) {
      return { reason: "ignored" };
    }

    const chainType = mapping.wallet.chain_type || resolvedNetwork.chainType;
    const uniqueSuffix = this.resolveIdempotencySuffix(activity);
    const asset = this.resolveAsset(activity);

    const event: DepositEvent = {
      source: "alchemy",
      status: "confirmed",
      idempotency_key: transactionHash,
      user_id: mapping.userId,
      wallet_address: recipient,
      amount,
      asset,
      tx_hash: transactionHash,
      block_number: blockNumber,
      log_index: uniqueSuffix,
      from_address: activity.fromAddress || null,
      to_address: recipient,
      asset_address: activity.rawContract?.address || activity.log?.address || null,
      chain_type: chainType,
      occurred_at: payload.createdAt || new Date().toISOString(),
      raw_payload: {
        webhook: payload,
        activity,
      },
    };

    this.applyWalletOwnership(event, mapping.wallet);

    if (this.isInternalFeeFundingEvent(event)) {
      return { reason: "ignored" };
    }

    return removed ? { reason: "reorg" } : { reason: "accepted", event };
  }

  private isSupportedActivity(activity: AlchemyAddressActivity): boolean {
    const category = activity.category?.toLowerCase();
    if (!category || !this.supportedCategories.has(category)) {
      return false;
    }

    if (activity.erc721TokenId || (activity.erc1155Metadata && activity.erc1155Metadata.length > 0)) {
      return false;
    }

    return true;
  }

  private resolveIdempotencySuffix(activity: AlchemyAddressActivity): string {
    if (activity.log?.logIndex) {
      return activity.log.logIndex;
    }

    if (activity.typeTraceAddress) {
      return activity.typeTraceAddress;
    }

    return "native";
  }

  private resolveAsset(activity: AlchemyAddressActivity): string {
    if (activity.asset && activity.asset.trim()) {
      return activity.asset.trim();
    }

    if (activity.category?.toLowerCase() === "external" || activity.category?.toLowerCase() === "internal") {
      return "ETH";
    }

    return "UNKNOWN";
  }

  private resolveNetwork(network?: string): NetworkResolution {
    const normalized = (network || "").toUpperCase();
    const map: Record<string, NetworkResolution> = {
      ETH_MAINNET: { chainType: "ethereum" },
      BASE_MAINNET: { chainType: "base" },
      BNB_MAINNET: { chainType: "bsc" },
      POLYGON_MAINNET: { chainType: "polygon" },
      POLYGON_MUMBAI: { chainType: "polygon" },
      SOLANA_MAINNET: { chainType: "solana" },
      SOLANA_DEVNET: { chainType: "solana" },
    };

    return map[normalized] || { chainType: normalized.toLowerCase() || null };
  }

  private hexToNumber(value: string | null | undefined): number | null {
    if (!value) {
      return null;
    }

    if (value.startsWith("0x")) {
      return Number.parseInt(value, 16);
    }

    const parsed = Number.parseInt(value, 10);
    return Number.isNaN(parsed) ? null : parsed;
  }

  private resolveAmount(activity: AlchemyAddressActivity): string | null {
    return normalizeEvmAmount({
      rawValue: activity.rawContract?.rawValue,
      decimals: activity.rawContract?.decimals,
      value: activity.value,
      isNative: !activity.rawContract?.address,
    });
  }

  private async buildSolanaDepositEvent(input: {
    payload: AlchemyWebhookPayload;
    transfer: Record<string, unknown>;
    txHash: string;
    blockNumber: number | null;
    resolvedNetwork: NetworkResolution;
    transferIndex: string;
    asset: string;
    assetAddress: string | null;
    recipientKeys: string[];
    senderKeys: string[];
    amountKeys?: string[];
    amountOverride?: string;
  }): Promise<DepositEvent | null> {
    const recipient = this.readString(input.transfer, input.recipientKeys)?.trim();
    if (!recipient) {
      return null;
    }

    const mapping = await this.mappingStoreService.getByTrackedAddress(recipient);
    if (!mapping) {
      return null;
    }

    const amount = input.amountOverride || this.readAmount(input.transfer, input.amountKeys || []);
    if (!amount) {
      return null;
    }

    const event: DepositEvent = {
      source: "alchemy",
      status: "confirmed",
      idempotency_key: `${input.txHash}:${input.transferIndex}`,
      user_id: mapping.userId,
      wallet_address: recipient,
      amount,
      asset: input.asset,
      tx_hash: input.txHash,
      block_number: input.blockNumber,
      log_index: input.transferIndex,
      from_address: this.readString(input.transfer, input.senderKeys) || null,
      to_address: recipient,
      asset_address: input.assetAddress,
      chain_type: mapping.wallet.chain_type || input.resolvedNetwork.chainType,
      occurred_at: input.payload.createdAt || new Date().toISOString(),
      raw_payload: {
        webhook: input.payload,
        transfer: input.transfer,
      },
    };

    this.applyWalletOwnership(event, mapping.wallet);

    return event;
  }

  private isInternalFeeFundingEvent(event: DepositEvent): boolean {
    const chainType = String(event.chain_type || "").trim().toLowerCase();
    const fromAddress = String(event.from_address || "").trim();
    if (!chainType || !fromAddress) {
      return false;
    }

    const configuredFundingAddress = this.feeFundingAddressByChainType[chainType];
    if (!configuredFundingAddress) {
      return false;
    }

    if (chainType === "solana") {
      return fromAddress === configuredFundingAddress;
    }

    return fromAddress.toLowerCase() === configuredFundingAddress.toLowerCase();
  }

  private applyWalletOwnership(event: DepositEvent, wallet: WalletMappingInput): void {
    event.wallet_id = wallet.wallet_id || null;
    event.organization_id = wallet.organization_id || null;
    event.branch_id = wallet.branch_id || null;
    event.terminal_id = wallet.terminal_id || null;
    event.payment_id = wallet.payment_id || null;
    event.supported_asset_id = wallet.supported_asset_id || null;
    event.derivation_index = wallet.derivation_index || null;
    if (wallet.network && wallet.network.trim()) {
      event.network = wallet.network;
    }
  }

  private readObjectArray(value: unknown): Array<Record<string, unknown>> {
    if (!Array.isArray(value)) {
      return [];
    }

    return value.filter((item): item is Record<string, unknown> => Boolean(item && typeof item === "object"));
  }

  private readNumberArray(value: unknown): number[] {
    if (!Array.isArray(value)) {
      return [];
    }

    return value
      .map((item) => {
        if (typeof item === "number" && Number.isFinite(item)) {
          return item;
        }

        if (typeof item === "string" && item.trim()) {
          const parsed = Number.parseInt(item, 10);
          return Number.isNaN(parsed) ? null : parsed;
        }

        return null;
      })
      .filter((item): item is number => item !== null);
  }

  private readSolanaTokenBalances(value: unknown): SolanaTokenBalanceEntry[] {
    if (!Array.isArray(value)) {
      return [];
    }

    return value
      .map((item) => {
        if (!item || typeof item !== "object") {
          return null;
        }

        const record = item as Record<string, unknown>;
        const uiTokenAmount =
          record.ui_token_amount && typeof record.ui_token_amount === "object"
            ? (record.ui_token_amount as Record<string, unknown>)
            : null;
        const amount =
          (uiTokenAmount && typeof uiTokenAmount.amount === "string" && uiTokenAmount.amount.trim()
            ? uiTokenAmount.amount.trim()
            : null) || null;
        const decimals =
          uiTokenAmount && typeof uiTokenAmount.decimals === "number" && Number.isFinite(uiTokenAmount.decimals)
            ? uiTokenAmount.decimals
            : null;
        const accountIndex =
          typeof record.account_index === "number" && Number.isFinite(record.account_index)
            ? record.account_index
            : null;
        const mint = typeof record.mint === "string" && record.mint.trim() ? record.mint.trim() : null;
        const owner = typeof record.owner === "string" && record.owner.trim() ? record.owner.trim() : null;

        if (accountIndex === null || !mint || !owner || !amount) {
          return null;
        }

        return {
          accountIndex,
          mint,
          owner,
          amount,
          decimals,
        } satisfies SolanaTokenBalanceEntry;
      })
      .filter((item): item is SolanaTokenBalanceEntry => Boolean(item));
  }

  private solanaTokenBalanceKey(balance: SolanaTokenBalanceEntry): string {
    return `${balance.accountIndex}:${balance.mint}`;
  }

  private resolveSolanaAssetSymbol(mint: string): string {
    return this.solanaMintSymbols[mint] || mint;
  }

  private parseMintSymbolMap(raw?: string): Record<string, string> {
    if (!raw) {
      return {};
    }

    try {
      const parsed = JSON.parse(raw) as Record<string, unknown>;
      return Object.entries(parsed).reduce<Record<string, string>>((accumulator, [mint, symbol]) => {
        if (typeof mint === "string" && mint.trim() && typeof symbol === "string" && symbol.trim()) {
          accumulator[mint.trim()] = symbol.trim();
        }
        return accumulator;
      }, {});
    } catch {
      throw new Error("SOLANA_MINT_SYMBOLS must be valid JSON");
    }
  }

  private extractSolanaAccountKeys(transactionEntries: Array<Record<string, unknown>>): string[] {
    for (const entry of transactionEntries) {
      const messages = this.readObjectArray(entry.message);
      for (const message of messages) {
        const accountKeys = message.account_keys;
        if (Array.isArray(accountKeys)) {
          const keys = accountKeys.filter(
            (item): item is string => typeof item === "string" && Boolean(item.trim())
          );
          if (keys.length > 0) {
            return keys;
          }
        }
      }
    }

    return [];
  }

  private readString(record: Record<string, unknown>, keys: string[]): string | null {
    for (const key of keys) {
      const value = record[key];
      if (typeof value === "string" && value.trim()) {
        return value;
      }
    }

    return null;
  }

  private readNumber(record: Record<string, unknown>, keys: string[]): number | null {
    for (const key of keys) {
      const value = record[key];
      if (typeof value === "number" && Number.isFinite(value)) {
        return value;
      }

      if (typeof value === "string" && value.trim()) {
        const parsed = Number.parseInt(value, 10);
        if (!Number.isNaN(parsed)) {
          return parsed;
        }
      }
    }

    return null;
  }

  private readAmount(record: Record<string, unknown>, keys: string[]): string | null {
    for (const key of keys) {
      const value = record[key];
      if (typeof value === "number" && Number.isFinite(value)) {
        return Math.trunc(value).toString(10);
      }

      if (typeof value === "string" && value.trim()) {
        return value;
      }
    }

    return null;
  }

  private readTokenAmount(record: Record<string, unknown>): string | null {
    const nested = record.tokenAmount;
    if (nested && typeof nested === "object") {
      const nestedRecord = nested as Record<string, unknown>;
      const uiAmountString = this.readAmount(nestedRecord, ["uiAmountString", "uiAmount"]);
      const rawAmount = this.readAmount(nestedRecord, ["rawTokenAmount", "tokenAmount", "amount", "value"]);
      const decimals = this.readNumber(nestedRecord, ["decimals"]);

      if (rawAmount) {
        return normalizeSolanaTokenAmount(rawAmount, decimals, uiAmountString);
      }

      if (uiAmountString) {
        return uiAmountString;
      }
    }

    const uiAmountString = this.readAmount(record, ["uiAmountString", "uiAmount"]);
    if (uiAmountString) {
      return uiAmountString;
    }

    const direct = this.readAmount(record, ["rawTokenAmount", "tokenAmount", "amount", "value"]);
    if (direct) {
      const decimals =
        this.readNumber(record, ["decimals"]) ??
        this.readNumber(record.rawContract as Record<string, unknown> ?? {}, ["decimals"]);

      return normalizeSolanaTokenAmount(direct, decimals);
    }

    return null;
  }
}
