import { randomUUID } from "node:crypto";
import { encodeFunctionData, getAddress } from "viem";
import type { AgentConfig } from "./config.js";
import { assertGasReserve, createChainClients } from "./chain.js";
import { PONS_LOCKER, ponsLockerAbi } from "./contracts.js";
import { decodeClaimReceipt } from "./receipt-data.js";
import { AgentState } from "./state.js";
import { sendTrackedTransaction } from "./broadcast.js";
import { discoverWalletLaunches } from "./discover-launches.js";

type ClaimResult = {
  token: string;
  mode: "simulated" | "confirmed" | "skipped";
  amount0?: string;
  amount1?: string;
  token0?: string;
  token1?: string;
  txHash?: string;
  executionId?: string;
  reason?: string;
};

const noFeesAvailable = (error: unknown) => {
  const message = error instanceof Error ? error.message : String(error);
  return message.includes("NoFeesToCollect") || message.toLowerCase().includes("no fees");
};

export const claimFeesOnce = async (config: AgentConfig): Promise<ClaimResult[]> => {
  if (!config.account) throw new Error("Execution wallet is required to inspect creator fee claims");
  const walletAddress = config.account.address;
  const state = new AgentState(config.stateDbPath);
  let discoveryError: unknown;
  if (state.listLaunches(walletAddress).length === 0) {
    try {
      const discovered = await discoverWalletLaunches(config);
      for (const launch of discovered) state.addLaunch(walletAddress, launch);
    } catch (error) {
      discoveryError = error;
    }
  }
  const launches = state.listLaunches(walletAddress);
  if (launches.length === 0) {
    state.close();
    if (discoveryError) {
      throw new Error(`无法发现该钱包发行的 Pons 代币：${discoveryError instanceof Error ? discoveryError.message : String(discoveryError)}`);
    }
    return [];
  }

  const clients = createChainClients(config);
  const { publicClient, walletClient } = clients;
  const results: ClaimResult[] = [];

  for (const launch of launches) {
    const token = getAddress(launch.token);
    let executionId: string | undefined;
    let txHash: `0x${string}` | undefined;
    let preparedTxHash: `0x${string}` | undefined;
    let reverted = false;
    try {
      const simulation = await publicClient.simulateContract({
        account: config.account,
        address: PONS_LOCKER,
        abi: ponsLockerAbi,
        functionName: "collectFees",
        args: [token],
      });
      const [amount0, amount1] = simulation.result;

      if (config.mode !== "live") {
        results.push({ token, mode: "simulated", amount0: amount0.toString(), amount1: amount1.toString() });
        continue;
      }
      if (!walletClient) throw new Error("Wallet client is unavailable in live mode");
      await assertGasReserve(publicClient, config.account.address, config.minGasReserveWei);

      executionId = randomUUID();
      state.startExecution(walletAddress, { id: executionId, kind: "claim", target: token, result: { token } });
      const data = encodeFunctionData({ abi: ponsLockerAbi, functionName: "collectFees", args: [token] });
      txHash = await sendTrackedTransaction(config, clients, { to: PONS_LOCKER, data }, (preparedHash) => {
        preparedTxHash = preparedHash;
        state.updateExecution(walletAddress, executionId!, "submitted", { txHash: preparedHash, result: { token } });
      });
      const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash, confirmations: 2, timeout: 120_000 });
      if (receipt.status !== "success") {
        reverted = true;
        throw new Error(`Fee claim reverted: ${txHash}`);
      }
      const claimed = decodeClaimReceipt(receipt, token);
      if (!claimed) throw new Error(`Claim succeeded but FeesClaimed could not be decoded: ${txHash}`);

      const result = {
        token,
        mode: "confirmed" as const,
        amount0: claimed.recipientAmount0.toString(),
        amount1: claimed.recipientAmount1.toString(),
        token0: claimed.token0,
        token1: claimed.token1,
        txHash,
        executionId,
      };
      state.recordClaimExecution(walletAddress, executionId, { txHash, token, ...claimed }, result);
      results.push(result);
    } catch (error) {
      if (executionId) {
        const recoverableHash = txHash ?? preparedTxHash;
        if (recoverableHash && !reverted) {
          state.updateExecution(walletAddress, executionId, "uncertain", { txHash: recoverableHash, error: error instanceof Error ? error.message : String(error) });
        } else {
          state.updateExecution(walletAddress, executionId, "failed", { txHash: recoverableHash, error: error instanceof Error ? error.message : String(error) });
        }
      }
      if (noFeesAvailable(error)) {
        results.push({ token, mode: "skipped", reason: "no creator fees available" });
        continue;
      }
      state.close();
      throw error;
    }
  }

  state.close();
  return results;
};
