import { expect } from "chai";
import { ethers } from "hardhat";
import {
  AgentSafetyManager,
  MockIdentityRegistry,
  MockBondsRegistry,
  MockReputationRegistry,
} from "../typechain-types";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";

describe("AgentSafetyManager", function () {
  let identity: MockIdentityRegistry;
  let bonds: MockBondsRegistry;
  let reputation: MockReputationRegistry;
  let safety: AgentSafetyManager;
  let owner: SignerWithAddress;
  let agent1: SignerWithAddress;
  let operator: SignerWithAddress;
  let random: SignerWithAddress;

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

    const IdentityFactory = await ethers.getContractFactory("MockIdentityRegistry");
    identity = (await IdentityFactory.deploy()) as MockIdentityRegistry;
    await identity.register(agent1.address);

    const BondsFactory = await ethers.getContractFactory("MockBondsRegistry");
    bonds = (await BondsFactory.deploy()) as MockBondsRegistry;

    const ReputationFactory = await ethers.getContractFactory("MockReputationRegistry");
    reputation = (await ReputationFactory.deploy()) as MockReputationRegistry;
    await reputation.setReputation(agent1.address, 80, 50);

    const SafetyFactory = await ethers.getContractFactory("AgentSafetyManager");
    safety = (await SafetyFactory.deploy(
      await identity.getAddress(),
      await bonds.getAddress(),
      await reputation.getAddress()
    )) as AgentSafetyManager;
  });

  describe("Deployment", function () {
    it("Should deploy with correct registry addresses", async function () {
      expect(await safety.identityRegistry()).to.equal(await identity.getAddress());
      expect(await safety.owner()).to.equal(owner.address);
    });

    it("Should reject zero addresses", async function () {
      const Factory = await ethers.getContractFactory("AgentSafetyManager");
      await expect(Factory.deploy(ethers.ZeroAddress, await bonds.getAddress(), await reputation.getAddress())).to.be.reverted;
    });
  });

  describe("Kill Switch (Freeze)", function () {
    it("Should freeze an agent", async function () {
      const tx = await safety.freezeAgent(agent1.address, "Suspicious activity detected");
      await expect(tx).to.emit(safety, "AgentFrozen");

      expect(await safety.isAgentFrozen(agent1.address)).to.be.true;
      expect(await safety.isAgentOperational(agent1.address)).to.be.false;
    });

    it("Should reject freeze from unauthorized caller", async function () {
      await expect(safety.connect(random).freezeAgent(agent1.address, "test")).to.be.reverted;
    });

    it("Should reject freeze of unregistered agent", async function () {
      await expect(safety.freezeAgent(random.address, "test")).to.be.reverted;
    });
  });

  describe("Safe Mode", function () {
    it("Should activate safe mode", async function () {
      const tx = await safety.activateSafeMode(agent1.address, "Precautionary safe mode");
      await expect(tx).to.emit(safety, "AgentSafeModeActivated");

      expect(await safety.isAgentInSafeMode(agent1.address)).to.be.true;
      expect(await safety.isAgentOperational(agent1.address)).to.be.false;
    });
  });

  describe("Reactivation", function () {
    it("Should reactivate a frozen agent", async function () {
      await safety.freezeAgent(agent1.address, "Frozen");
      const tx = await safety.reactivateAgent(agent1.address, "Issue resolved");
      await expect(tx).to.emit(safety, "AgentReactivated");

      expect(await safety.isAgentOperational(agent1.address)).to.be.true;
      expect(await safety.isAgentFrozen(agent1.address)).to.be.false;
    });

    it("Should reactivate from safe mode", async function () {
      await safety.activateSafeMode(agent1.address, "Safe mode");
      await safety.reactivateAgent(agent1.address, "Cleared");
      expect(await safety.isAgentOperational(agent1.address)).to.be.true;
    });

    it("Should reject reactivation of already active agent", async function () {
      await safety.freezeAgent(agent1.address, "Frozen");
      await safety.reactivateAgent(agent1.address, "Cleared");
      await expect(safety.reactivateAgent(agent1.address, "Already active")).to.be.reverted;
    });
  });

  describe("Auto-trigger Safety", function () {
    it("Should auto-freeze when reputation below freeze threshold", async function () {
      await reputation.setReputation(agent1.address, 5, 50); // Below freeze threshold (10)
      await safety.checkAndEnforceSafety(agent1.address);
      expect(await safety.isAgentFrozen(agent1.address)).to.be.true;
    });

    it("Should auto-safe-mode when reputation below safe mode threshold", async function () {
      await reputation.setReputation(agent1.address, 20, 50); // Below safe mode (30) but above freeze (10)
      await safety.checkAndEnforceSafety(agent1.address);
      expect(await safety.isAgentInSafeMode(agent1.address)).to.be.true;
    });

    it("Should not trigger when reputation is healthy", async function () {
      await reputation.setReputation(agent1.address, 80, 50);
      await safety.checkAndEnforceSafety(agent1.address);
      expect(await safety.isAgentOperational(agent1.address)).to.be.true;
    });
  });

  describe("Safety Operators", function () {
    it("Should allow authorized operator to freeze agents", async function () {
      await safety.setSafetyOperator(operator.address, true);
      await safety.connect(operator).freezeAgent(agent1.address, "Operator freeze");
      expect(await safety.isAgentFrozen(agent1.address)).to.be.true;
    });

    it("Should reject unauthorized operators", async function () {
      await expect(safety.connect(random).freezeAgent(agent1.address, "Unauthorized")).to.be.reverted;
    });
  });

  describe("Audit Trail", function () {
    it("Should log safety events", async function () {
      await safety.freezeAgent(agent1.address, "Frozen");
      await safety.reactivateAgent(agent1.address, "Cleared");

      const eventIds = await safety.getAgentSafetyEvents(agent1.address);
      expect(eventIds.length).to.equal(2);

      const logCount = await safety.getSafetyLogCount();
      expect(logCount).to.equal(2);
    });
  });

  describe("Admin", function () {
    it("Should update thresholds", async function () {
      await safety.setThresholds(40, 15);
      expect(await safety.safeModeThreshold()).to.equal(40);
      expect(await safety.freezeThreshold()).to.equal(15);
    });

    it("Should reject invalid thresholds", async function () {
      await expect(safety.setThresholds(10, 20)).to.be.reverted; // safe mode must be above freeze
      await expect(safety.setThresholds(110, 5)).to.be.reverted; // out of range
    });

    it("Should transfer ownership", async function () {
      await safety.transferOwnership(agent1.address);
      expect(await safety.owner()).to.equal(agent1.address);
    });
  });

  describe("View Functions", function () {
    it("Should return correct safety stats", async function () {
      await safety.freezeAgent(agent1.address, "Test");
      const [frozen, safeMode, totalEvents] = await safety.getSafetyStats();
      expect(frozen).to.equal(1);
      expect(safeMode).to.equal(0);
      expect(totalEvents).to.equal(1);
    });

    it("Should return Active for agents with no safety record", async function () {
      const [status] = await safety.getAgentStatus(random.address);
      expect(status).to.equal(0); // Active
    });
  });
});
