import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import {
  encodeAbiParameters,
  encodeEventTopics,
  type Address,
  type Log,
  type TransactionReceipt,
} from "viem";
import type { AgentConfig } from "../src/config.js";
import { PONS_FACTORY, PONS_LOCKER, PONS_WETH, erc20Abi, ponsFactoryAbi, ponsLockerAbi } from "../src/contracts.js";
import { reconcileExecutions } from "../src/reconcile.js";
import { AgentState } from "../src/state.js";

const address = (digit: string) => `0x${digit.repeat(40)}` as Address;
const hash = (digit: string) => `0x${digit.repeat(64)}` as `0x${string}`;
const executionWallet = address("e");

const receipt = (status: "success" | "reverted", logs: Log[] = []) => ({ status, logs }) as TransactionReceipt;
const config = (stateDbPath: string) => ({ stateDbPath, account: { address: executionWallet } }) as unknown as AgentConfig;

test("reconcile restores a confirmed launch from its factory receipt", async () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-reconcile-launch-"));
  const path = join(directory, "state.db");
  const state = new AgentState(path);
  const txHash = hash("1");
  const token = address("2");
  const pool = address("3");
  try {
    state.startExecution(executionWallet, { id: "launch-1", kind: "launch", target: "#alpha", result: { name: "Alpha", symbol: "ALPHA", topic: "#alpha", totalValueEth: "0.01" } });
    state.updateExecution(executionWallet, "launch-1", "submitted", { txHash });
    state.close();

    const topics = encodeEventTopics({
      abi: ponsFactoryAbi,
      eventName: "TokenLaunched",
      args: { token, deployer: address("4"), dexFactory: address("5") },
    });
    const data = encodeAbiParameters([
      { type: "address" }, { type: "address" }, { type: "uint256" }, { type: "uint256" },
      { type: "uint256" }, { type: "uint256" }, { type: "uint256" },
    ], [address("6"), pool, 0n, 0n, 1n, 0n, 10n]);
    const result = await reconcileExecutions(config(path), {
      getTransactionReceipt: async () => receipt("success", [{ address: PONS_FACTORY, topics, data } as Log]),
    });

    const verified = new AgentState(path);
    assert.equal(result[0]?.status, "confirmed");
    assert.equal(verified.listLaunches(executionWallet)[0]?.token, token);
    assert.equal(verified.hasLaunchedTopic(executionWallet, "#alpha"), true);
    assert.equal(verified.getExecution(executionWallet, "launch-1")?.status, "confirmed");
    verified.close();
  } finally {
    if (state.database.open) state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("reconcile rebuilds the 50 percent buyback allocation from a claim receipt", async () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-reconcile-claim-"));
  const path = join(directory, "state.db");
  const state = new AgentState(path);
  const txHash = hash("2");
  const token = address("7");
  const token0 = PONS_WETH;
  const token1 = address("8");
  try {
    state.startExecution(executionWallet, { id: "claim-1", kind: "claim", target: token, result: { token } });
    state.updateExecution(executionWallet, "claim-1", "uncertain", { txHash });
    state.close();

    const topics = encodeEventTopics({
      abi: ponsLockerAbi,
      eventName: "FeesClaimed",
      args: { token, caller: address("9") },
    });
    const data = encodeAbiParameters([
      { type: "address" }, { type: "address" }, { type: "uint256" }, { type: "uint256" },
      { type: "uint256" }, { type: "uint256" },
    ], [token0, token1, 100n, 40n, 30n, 12n]);
    const result = await reconcileExecutions(config(path), {
      getTransactionReceipt: async () => receipt("success", [{ address: PONS_LOCKER, topics, data } as Log]),
    });

    const verified = new AgentState(path);
    assert.equal(result[0]?.status, "confirmed");
    assert.equal(verified.getBucketBalance(executionWallet, "buyback", token0), 50n);
    assert.equal(verified.getBucketBalance(executionWallet, "treasury", token0), 50n);
    assert.equal(verified.getExecution(executionWallet, "claim-1")?.status, "confirmed");
    verified.close();
  } finally {
    if (state.database.open) state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("reconcile releases a reverted buyback but keeps a successful debit", async () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-reconcile-buyback-"));
  const path = join(directory, "state.db");
  const token = address("a");
  const wallet = address("e");
  const state = new AgentState(path);
  try {
    state.addLedgerEntry(wallet, { id: "allocation", bucket: "buyback", direction: "credit", asset: PONS_WETH, amount: "100" });
    state.startBuybackExecutionAndReserve(wallet, { id: "buyback-reverted", target: token, amount: "40" }, { id: "buyback-reverted:buyback-reservation", asset: PONS_WETH });
    state.updateExecution(wallet, "buyback-reverted", "submitted", { txHash: hash("3") });
    state.startBuybackExecutionAndReserve(wallet, { id: "buyback-success", target: token, amount: "50" }, { id: "buyback-success:buyback-reservation", asset: PONS_WETH });
    state.updateExecution(wallet, "buyback-success", "uncertain", { txHash: hash("4") });
    state.close();

    const topics = encodeEventTopics({ abi: erc20Abi, eventName: "Transfer", args: { from: address("f"), to: wallet } });
    const data = encodeAbiParameters([{ type: "uint256" }], [12n]);
    const result = await reconcileExecutions({ stateDbPath: path, account: { address: wallet } } as unknown as AgentConfig, {
      getTransactionReceipt: async ({ hash: requested }) => requested === hash("3")
        ? receipt("reverted")
        : receipt("success", [{ address: token, topics, data } as Log]),
    });

    const verified = new AgentState(path);
    assert.equal(result.find((item) => item.executionId === "buyback-reverted")?.status, "failed");
    assert.equal(result.find((item) => item.executionId === "buyback-success")?.status, "confirmed");
    assert.equal(verified.getBucketBalance(wallet, "buyback", PONS_WETH), 50n);
    verified.close();
  } finally {
    if (state.database.open) state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});

test("reconcile credits WETH from a confirmed internal fee conversion without publishing it as a buyback", async () => {
  const directory = mkdtempSync(join(tmpdir(), "zoi-agent-reconcile-conversion-"));
  const path = join(directory, "state.db");
  const inputAsset = address("b");
  const wallet = address("c");
  const txHash = hash("5");
  const state = new AgentState(path);
  try {
    state.addLedgerEntry(wallet, { id: "allocation", bucket: "buyback", direction: "credit", asset: inputAsset, amount: "100" });
    state.startBuybackExecutionAndReserve(wallet, {
      id: "conversion-1",
      target: inputAsset,
      amount: "100",
      result: { action: "convert", inputAsset, outputAsset: PONS_WETH, amountIn: "100" },
    }, { id: "conversion-1:buyback-reservation", asset: inputAsset });
    state.updateExecution(wallet, "conversion-1", "uncertain", { txHash });
    state.close();

    const topics = encodeEventTopics({ abi: erc20Abi, eventName: "Transfer", args: { from: address("d"), to: wallet } });
    const data = encodeAbiParameters([{ type: "uint256" }], [25n]);
    const liveConfig = { stateDbPath: path, account: { address: wallet } } as unknown as AgentConfig;
    const result = await reconcileExecutions(liveConfig, {
      getTransactionReceipt: async () => receipt("success", [{ address: PONS_WETH, topics, data } as Log]),
    });

    const verified = new AgentState(path);
    assert.equal(result[0]?.status, "confirmed");
    assert.equal(verified.getBucketBalance(wallet, "buyback", inputAsset), 0n);
    assert.equal(verified.getBucketBalance(wallet, "buyback", PONS_WETH), 25n);
    assert.equal(verified.listConfirmedExecutionsWithoutPublicEvent(wallet).length, 0);
    verified.close();
  } finally {
    if (state.database.open) state.close();
    rmSync(directory, { recursive: true, force: true });
  }
});
