import { getAddress, TransactionReceiptNotFoundError, type TransactionReceipt } from "viem";
import type { AgentConfig } from "./config.js";
import { createChainClients } from "./chain.js";
import { PONS_WETH } from "./contracts.js";
import { decodeBuybackAcquired, decodeClaimReceipt, decodeLaunchReceipt } from "./receipt-data.js";
import { AgentState } from "./state.js";

export type ReconcileResult = {
  executionId: string;
  kind: "launch" | "claim" | "buyback";
  txHash: string;
  status: "pending" | "confirmed" | "failed" | "uncertain";
  detail?: string;
};

type ReceiptClient = {
  getTransactionReceipt(args: { hash: `0x${string}` }): Promise<TransactionReceipt>;
};

const parseResult = (value: string | null) => {
  if (!value) return {};
  try {
    const parsed: unknown = JSON.parse(value);
    return parsed && typeof parsed === "object" && !Array.isArray(parsed)
      ? parsed as Record<string, unknown>
      : {};
  } catch {
    return {};
  }
};

const isReceiptPending = (error: unknown) => error instanceof TransactionReceiptNotFoundError
  || (error instanceof Error && /receipt|transaction.*not found|not yet mined/i.test(error.message));

/**
 * Reconcile only transactions that were already recorded as broadcast.
 * A planned execution without a hash is intentionally left for operator review.
 */
export const reconcileExecutions = async (config: AgentConfig, receiptClient?: ReceiptClient): Promise<ReconcileResult[]> => {
  if (!config.account) throw new Error("Execution wallet is required to reconcile transactions");
  const walletAddress = config.account.address;
  const initialState = new AgentState(config.stateDbPath);
  const executions = initialState.listRecoverableExecutions(walletAddress);
  initialState.close();
  if (executions.length === 0) return [];

  const publicClient: ReceiptClient = receiptClient ?? createChainClients(config).publicClient;
  const results: ReconcileResult[] = [];

  for (const execution of executions) {
    let receipt;
    try {
      receipt = await publicClient.getTransactionReceipt({ hash: execution.txHash as `0x${string}` });
    } catch (error) {
      if (isReceiptPending(error)) {
        results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "pending" });
        continue;
      }
      throw error;
    }

    const state = new AgentState(config.stateDbPath);
    if (receipt.status !== "success") {
      let detail = "Transaction reverted on Robinhood Chain";
      if (execution.kind === "buyback" && execution.amount) {
        const previous = parseResult(execution.resultJson);
        const reservationAsset = typeof previous.inputAsset === "string" ? previous.inputAsset : PONS_WETH;
        const released = state.releaseBucketFunds(walletAddress, `${execution.id}:buyback-reservation`, "buyback", reservationAsset, execution.amount);
        if (!released) detail += "; buyback reservation was already released or could not be matched";
      }
      state.updateExecution(walletAddress, execution.id, "failed", { txHash: execution.txHash, error: detail });
      state.close();
      results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "failed", detail });
      continue;
    }

    const previous = parseResult(execution.resultJson);
    if (execution.kind === "launch") {
      const launched = decodeLaunchReceipt(receipt);
      if (!launched || typeof previous.name !== "string" || typeof previous.symbol !== "string") {
        const detail = "Launch receipt succeeded but its TokenLaunched metadata is incomplete";
        state.updateExecution(walletAddress, execution.id, "uncertain", { txHash: execution.txHash, error: detail });
        state.close();
        results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "uncertain", detail });
        continue;
      }
      const result = { ...previous, token: launched.token, pool: launched.pool, txHash: execution.txHash, executionId: execution.id };
      state.recordLaunchExecution(walletAddress, execution.id, {
        token: launched.token,
        pool: launched.pool,
        txHash: execution.txHash,
        name: previous.name,
        symbol: previous.symbol,
        topic: typeof previous.topic === "string" ? previous.topic : undefined,
      }, result);
      state.close();
      results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "confirmed" });
      continue;
    }

    if (execution.kind === "claim") {
      if (!execution.target) {
        const detail = "Claim execution has no token target";
        state.updateExecution(walletAddress, execution.id, "uncertain", { txHash: execution.txHash, error: detail });
        state.close();
        results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "uncertain", detail });
        continue;
      }
      const token = getAddress(execution.target);
      const claimed = decodeClaimReceipt(receipt, token);
      if (!claimed) {
        const detail = "Claim receipt succeeded but FeesClaimed was not found";
        state.updateExecution(walletAddress, execution.id, "uncertain", { txHash: execution.txHash, error: detail });
        state.close();
        results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "uncertain", detail });
        continue;
      }
      const result = {
        ...previous,
        token,
        token0: claimed.token0,
        token1: claimed.token1,
        amount0: claimed.recipientAmount0.toString(),
        amount1: claimed.recipientAmount1.toString(),
        txHash: execution.txHash,
        executionId: execution.id,
      };
      state.recordClaimExecution(walletAddress, execution.id, { txHash: execution.txHash, token, ...claimed }, result);
      state.close();
      results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "confirmed" });
      continue;
    }

    if (previous.action === "convert") {
      if (!execution.target || !config.account) {
        const detail = "Fee conversion receipt needs its input asset and execution wallet for recovery";
        state.updateExecution(walletAddress, execution.id, "uncertain", { txHash: execution.txHash, error: detail });
        state.close();
        results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "uncertain", detail });
        continue;
      }
      const outputAsset = typeof previous.outputAsset === "string" ? getAddress(previous.outputAsset) : PONS_WETH;
      const amountOut = decodeBuybackAcquired(receipt, outputAsset, config.account.address);
      if (amountOut === 0n) {
        const detail = "Fee conversion succeeded but its WETH transfer could not be decoded";
        state.updateExecution(walletAddress, execution.id, "uncertain", { txHash: execution.txHash, error: detail });
        state.close();
        results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "uncertain", detail });
        continue;
      }
      const result = { ...previous, inputAsset: getAddress(execution.target), outputAsset, amountOut: amountOut.toString(), txHash: execution.txHash, executionId: execution.id };
      state.recordConversionExecution(walletAddress, execution.id, {
        txHash: execution.txHash,
        inputAsset: getAddress(execution.target),
        inputAmount: execution.amount ?? "0",
        outputAsset,
        outputAmount: amountOut.toString(),
      }, result);
      state.close();
      results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "confirmed" });
      continue;
    }

    if (!execution.target) {
      const detail = "Buyback execution has no token target";
      state.updateExecution(walletAddress, execution.id, "uncertain", { txHash: execution.txHash, error: detail });
      state.close();
      results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "uncertain", detail });
      continue;
    }
    if (!config.account) {
      const detail = "Buyback receipt needs the execution wallet to decode acquired tokens";
      state.updateExecution(walletAddress, execution.id, "uncertain", { txHash: execution.txHash, error: detail });
      state.close();
      results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "uncertain", detail });
      continue;
    }
    const acquiredAmount = decodeBuybackAcquired(receipt, getAddress(execution.target), config.account.address);
    if (acquiredAmount === 0n) {
      const detail = "Buyback succeeded but its token transfer could not be decoded";
      state.updateExecution(walletAddress, execution.id, "uncertain", { txHash: execution.txHash, error: detail });
      state.close();
      results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "uncertain", detail });
      continue;
    }
    const result = {
      ...previous,
      token: getAddress(execution.target),
      acquiredAmount: acquiredAmount.toString(),
      txHash: execution.txHash,
      executionId: execution.id,
    };
    state.updateExecution(walletAddress, execution.id, "confirmed", { txHash: execution.txHash, result });
    state.close();
    results.push({ executionId: execution.id, kind: execution.kind, txHash: execution.txHash, status: "confirmed" });
  }

  return results;
};
