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

describe("AereBlockSTMRegistry", () => {
  let alice, bob, target;
  let reg;
  const SELECTOR = "0x12345678";
  const SLOT_A = ethers.keccak256(ethers.toUtf8Bytes("slot-a"));
  const SLOT_B = ethers.keccak256(ethers.toUtf8Bytes("slot-b"));

  beforeEach(async () => {
    [alice, bob, target] = await ethers.getSigners();
    const F = await ethers.getContractFactory("AereBlockSTMRegistry");
    reg = await F.deploy();
    await reg.waitForDeployment();
  });

  it("first publisher claims (target, selector); only they can update", async () => {
    // AUDIT FIX (HIGH #27): first publisher must be target itself OR target's owner.
    // We have `target` as an EOA so we use it as msg.sender (== target.address).
    await reg.connect(target).publish(target.address, SELECTOR, [
      { slotPrefix: SLOT_A, mode: 0 /*READ*/ },
      { slotPrefix: SLOT_B, mode: 1 /*WRITE*/ },
    ]);
    expect(await reg.publisherOf(target.address, SELECTOR)).to.equal(target.address);

    // Random alice cannot squat someone else's (target, selector).
    await expect(
      reg.connect(alice).publish(target.address, SELECTOR, [{ slotPrefix: SLOT_A, mode: 0 }])
    ).to.be.revertedWithCustomError(reg, "NotPublisher");
    // Bob can't either.
    await expect(
      reg.connect(bob).publish(target.address, SELECTOR, [{ slotPrefix: SLOT_A, mode: 0 }])
    ).to.be.revertedWithCustomError(reg, "NotPublisher");
  });

  it("hintsFor returns the latest set + count is correct", async () => {
    await reg.connect(target).publish(target.address, SELECTOR, [
      { slotPrefix: SLOT_A, mode: 2 /*READWRITE*/ },
    ]);
    const out = await reg.hintsFor(target.address, SELECTOR);
    expect(out.publisher).to.equal(target.address);
    expect(out.hints.length).to.equal(1);
    expect(out.hints[0].slotPrefix).to.equal(SLOT_A);
    expect(out.hints[0].mode).to.equal(2);
  });

  it("publish replaces previous hints (not append)", async () => {
    await reg.connect(target).publish(target.address, SELECTOR, [
      { slotPrefix: SLOT_A, mode: 0 }, { slotPrefix: SLOT_B, mode: 0 },
    ]);
    await reg.connect(target).publish(target.address, SELECTOR, [
      { slotPrefix: SLOT_A, mode: 1 },
    ]);
    const out = await reg.hintsFor(target.address, SELECTOR);
    expect(out.hints.length).to.equal(1);
    expect(out.hints[0].mode).to.equal(1);
  });

  it("rejects > 64 hints", async () => {
    const hints = [];
    for (let i = 0; i < 65; i++) hints.push({ slotPrefix: SLOT_A, mode: 0 });
    await expect(
      reg.connect(target).publish(target.address, SELECTOR, hints)
    ).to.be.revertedWithCustomError(reg, "TooManyHints");
  });

  it("clear only by publisher; emits", async () => {
    await reg.connect(target).publish(target.address, SELECTOR, [{ slotPrefix: SLOT_A, mode: 0 }]);
    await expect(reg.connect(bob).clear(target.address, SELECTOR)).to.be.revertedWithCustomError(reg, "NotPublisher");
    await expect(reg.connect(target).clear(target.address, SELECTOR)).to.emit(reg, "HintsCleared");
    const out = await reg.hintsFor(target.address, SELECTOR);
    expect(out.hints.length).to.equal(0);
  });
});
