import assert from "node:assert/strict";
import test from "node:test";
import {
  encodeAbiParameters,
  encodeEventTopics,
  getAddress,
  padHex,
  type Address,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import type { AgentConfig } from "../src/config.js";
import { PONS_FACTORY, ponsFactoryAbi } from "../src/contracts.js";
import { blockscoutLaunchLogsUrl, discoverWalletLaunches } from "../src/discover-launches.js";

const address = (digit: string) => getAddress(`0x${digit.repeat(40)}`);
const account = privateKeyToAccount(`0x${"44".repeat(32)}`);
const config = { account } as unknown as AgentConfig;

const launchLog = (deployer: Address, token = address("1")) => ({
  address: PONS_FACTORY,
  topics: encodeEventTopics({
    abi: ponsFactoryAbi,
    eventName: "TokenLaunched",
    args: { token, deployer, dexFactory: address("2") },
  }),
  data: encodeAbiParameters([
    { type: "address" }, { type: "address" }, { type: "uint256" }, { type: "uint256" },
    { type: "uint256" }, { type: "uint256" }, { type: "uint256" },
  ], [address("3"), address("5"), 0n, 0n, 1n, 0n, 10n]),
  blockNumber: "0x895000",
  timeStamp: "0x66851d80",
  transactionHash: `0x${"6".repeat(64)}`,
});

test("Blockscout discovery filters by the active wallet and imports launch metadata", async () => {
  let requestedUrl: URL | null = null;
  const launches = await discoverWalletLaunches(config, {
    fetchImpl: (async (input) => {
      requestedUrl = new URL(String(input));
      return Response.json({
        status: "1",
        message: "OK",
        result: [launchLog(account.address), launchLog(address("9"), address("8"))],
      });
    }) as typeof fetch,
    client: {
      readContract: async (request) => request.functionName === "name" ? "Signal" : "SGNL",
      getBlock: async () => ({ timestamp: 0n }),
    },
  });

  assert.equal(requestedUrl?.searchParams.get("topic2"), padHex(account.address, { size: 32 }));
  assert.equal(launches.length, 1);
  assert.equal(launches[0]?.token, address("1"));
  assert.equal(launches[0]?.pool, address("5"));
  assert.equal(launches[0]?.name, "Signal");
  assert.equal(launches[0]?.symbol, "SGNL");
  assert.equal(launches[0]?.createdAt, "2024-07-03T09:44:32.000Z");
});

test("Blockscout no-record responses are treated as an empty wallet history", async () => {
  const launches = await discoverWalletLaunches(config, {
    fetchImpl: (async () => Response.json({ status: "0", message: "No records found", result: "No records found" })) as typeof fetch,
    client: {
      readContract: async () => "unused",
      getBlock: async () => ({ timestamp: 0n }),
    },
  });
  assert.deepEqual(launches, []);
});

test("the Blockscout query starts at the Pons factory deployment block", () => {
  const url = blockscoutLaunchLogsUrl(account.address);
  assert.equal(url.searchParams.get("fromBlock"), "8991118");
  assert.equal(url.searchParams.get("address"), PONS_FACTORY);
});

test("RPC block chunks recover wallet launches when Blockscout is unavailable", async () => {
  const token = address("a");
  const pool = address("b");
  let logQueries = 0;
  const launches = await discoverWalletLaunches(config, {
    fetchImpl: (async () => new Response("upstream error", { status: 500 })) as typeof fetch,
    client: {
      getBlockNumber: async () => 8_993_200n,
      getLogs: async () => {
        logQueries += 1;
        return logQueries === 2 ? [{
          args: { token, deployer: account.address, pool },
          blockNumber: 8_993_000n,
          transactionHash: `0x${"c".repeat(64)}`,
        }] : [];
      },
      readContract: async (request) => request.functionName === "name" ? "Recovered" : "RCVR",
      getBlock: async () => ({ timestamp: 1_720_000_000n }),
    },
  });
  assert.equal(logQueries, 2);
  assert.equal(launches[0]?.token, token);
  assert.equal(launches[0]?.pool, pool);
  assert.equal(launches[0]?.symbol, "RCVR");
});
