const { expect } = require("chai");
const { ethers, network } = require("hardhat");
const path = require("path");

const mockRouterArtifact = require(path.join(
  __dirname,
  "../../out/CCIPWETH9Bridge.t.sol/MockCCIPRouter.json"
));

async function expectEvent(txPromise, contract, eventName) {
  const tx = await txPromise;
  const receipt = await tx.wait();
  const foundEvent = receipt.logs.some((log) => {
    try {
      return contract.interface.parseLog(log)?.name === eventName;
    } catch (error) {
      return false;
    }
  });

  expect(foundEvent).to.equal(true);
}

describe("CCIP Integration", function () {
  let ccipLogger, ccipReporter;
  let owner, relayer;
  let mockRouter, mockSourceRouter;

  beforeEach(async function () {
    [owner, relayer] = await ethers.getSigners();

    // Reuse the Foundry-built mock router so this suite doesn't depend on a
    // separate Hardhat-only mock artifact.
    const MockRouter = new ethers.ContractFactory(
      mockRouterArtifact.abi,
      mockRouterArtifact.bytecode.object,
      owner
    );
    mockRouter = await MockRouter.deploy();
    await mockRouter.waitForDeployment();

    mockSourceRouter = await MockRouter.deploy();
    await mockSourceRouter.waitForDeployment();

    // Deploy CCIPLogger (Ethereum receiver)
    const CCIPLogger = await ethers.getContractFactory("CCIPLogger");
    ccipLogger = await CCIPLogger.deploy(
      await mockRouter.getAddress(),
      ethers.ZeroAddress, // No authorized signer for basic test
      "0x000000000000008a" // Chain-138 selector
    );
    await ccipLogger.waitForDeployment();

    // Deploy CCIPTxReporter (Chain-138 sender)
    const CCIPTxReporter = await ethers.getContractFactory("CCIPTxReporter");
    ccipReporter = await CCIPTxReporter.deploy(
      await mockSourceRouter.getAddress(),
      "0x500147", // Ethereum Mainnet selector
      await ccipLogger.getAddress()
    );
    await ccipReporter.waitForDeployment();
  });

  describe("CCIPTxReporter", function () {
    it("Should report a single transaction", async function () {
      const txHash = ethers.keccak256(ethers.toUtf8Bytes("test-tx"));
      const fromAddr = owner.address;
      const toAddr = relayer.address;
      const value = ethers.parseEther("1.0");

      await expectEvent(
        ccipReporter.reportTx(txHash, fromAddr, toAddr, value, "0x", {
          value: ethers.parseEther("0.01"),
        }),
        ccipReporter,
        "SingleTxReported"
      );
    });

    it("Should report a batch of transactions", async function () {
      const batchId = ethers.keccak256(ethers.toUtf8Bytes("test-batch"));
      const txHashes = [
        ethers.keccak256(ethers.toUtf8Bytes("tx1")),
        ethers.keccak256(ethers.toUtf8Bytes("tx2")),
      ];
      const froms = [owner.address, relayer.address];
      const tos = [relayer.address, owner.address];
      const values = [ethers.parseEther("1.0"), ethers.parseEther("2.0")];

      await expectEvent(
        ccipReporter.reportBatch(batchId, txHashes, froms, tos, values, "0x", {
          value: ethers.parseEther("0.01"),
        }),
        ccipReporter,
        "BatchReported"
      );
    });

    it("Should estimate fee correctly", async function () {
      const txHashes = [ethers.keccak256(ethers.toUtf8Bytes("tx1"))];
      const froms = [owner.address];
      const tos = [relayer.address];
      const values = [ethers.parseEther("1.0")];

      const fee = await ccipReporter.estimateFee(
        txHashes,
        froms,
        tos,
        values
      );

      expect(fee > 0n).to.equal(true);
    });
  });

  describe("CCIPLogger", function () {
    it("Should receive and log transactions", async function () {
      const batchId = ethers.keccak256(ethers.toUtf8Bytes("test-batch"));
      const txHashes = [ethers.keccak256(ethers.toUtf8Bytes("tx1"))];
      const froms = [owner.address];
      const tos = [relayer.address];
      const values = [ethers.parseEther("1.0")];

      const payload = ethers.AbiCoder.defaultAbiCoder().encode(
        ["bytes32", "bytes32[]", "address[]", "address[]", "uint256[]", "bytes"],
        [batchId, txHashes, froms, tos, values, "0x"]
      );

      // Simulate CCIP message delivery
      const message = {
        messageId: ethers.keccak256(ethers.toUtf8Bytes("test-message")),
        sourceChainSelector: "0x000000000000008a",
        sender: ethers.zeroPadValue(await ccipReporter.getAddress(), 32),
        data: payload,
        tokenAmounts: [],
      };

      const routerAddress = await mockRouter.getAddress();
      await network.provider.request({
        method: "hardhat_impersonateAccount",
        params: [routerAddress],
      });
      await network.provider.send("hardhat_setBalance", [
        routerAddress,
        "0x56BC75E2D63100000",
      ]);

      const routerSigner = await ethers.getSigner(routerAddress);

      await expectEvent(
        ccipLogger.connect(routerSigner).ccipReceive(message),
        ccipLogger,
        "RemoteBatchLogged"
      );

      await network.provider.request({
        method: "hardhat_stopImpersonatingAccount",
        params: [routerAddress],
      });
    });

    it("Should prevent replay attacks", async function () {
      const batchId = ethers.keccak256(ethers.toUtf8Bytes("replay-test"));
      const txHashes = [ethers.keccak256(ethers.toUtf8Bytes("tx1"))];
      const froms = [owner.address];
      const tos = [relayer.address];
      const values = [ethers.parseEther("1.0")];

      const payload = ethers.AbiCoder.defaultAbiCoder().encode(
        ["bytes32", "bytes32[]", "address[]", "address[]", "uint256[]", "bytes"],
        [batchId, txHashes, froms, tos, values, "0x"]
      );

      const message = {
        messageId: ethers.keccak256(ethers.toUtf8Bytes("test-message-1")),
        sourceChainSelector: "0x000000000000008a",
        sender: ethers.zeroPadValue(await ccipReporter.getAddress(), 32),
        data: payload,
        tokenAmounts: [],
      };

      const routerAddress = await mockRouter.getAddress();
      await network.provider.request({
        method: "hardhat_impersonateAccount",
        params: [routerAddress],
      });
      await network.provider.send("hardhat_setBalance", [
        routerAddress,
        "0x56BC75E2D63100000",
      ]);

      const routerSigner = await ethers.getSigner(routerAddress);

      // First delivery should succeed
      await ccipLogger.connect(routerSigner).ccipReceive(message);

      // Second delivery with same batchId should fail
      const message2 = {
        ...message,
        messageId: ethers.keccak256(ethers.toUtf8Bytes("test-message-2")),
      };

      let reverted = false;
      try {
        await ccipLogger.connect(routerSigner).ccipReceive(message2);
      } catch (error) {
        reverted = error.message.includes("CCIPLogger: batch already processed");
      }

      expect(reverted).to.equal(true);

      await network.provider.request({
        method: "hardhat_stopImpersonatingAccount",
        params: [routerAddress],
      });
    });
  });
});
