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

describe("AgentSLAEnforcer", function () {
  let identity: MockIdentityRegistry;
  let bonds: MockBondsRegistry;
  let reputation: MockReputationRegistry;
  let sla: AgentSLAEnforcer;
  let owner: SignerWithAddress;
  let agent1: SignerWithAddress;
  let monitor: SignerWithAddress;
  let random: SignerWithAddress;

  const UPTIME = 9950;    // 99.5%
  const RESPONSE = 1000;  // 1000ms
  const ACCURACY = 9800;  // 98%
  const PENALTY_RATE = 500; // 5%
  const DURATION = 30 * 24 * 3600; // 30 days
  const STAKE = ethers.parseEther("1");

  beforeEach(async function () {
    [owner, agent1, monitor, 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;

    const SLAFactory = await ethers.getContractFactory("AgentSLAEnforcer");
    sla = (await SLAFactory.deploy(
      await identity.getAddress(),
      await bonds.getAddress(),
      await reputation.getAddress()
    )) as AgentSLAEnforcer;

    await sla.setMonitor(monitor.address, true);
  });

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

  describe("SLA Creation", function () {
    it("Should create SLA with stake", async function () {
      const tx = await sla.connect(agent1).createSLA(UPTIME, RESPONSE, ACCURACY, PENALTY_RATE, DURATION, { value: STAKE });
      await expect(tx).to.emit(sla, "SLACreated");

      const slaData = await sla.slas(0);
      expect(slaData.agent).to.equal(agent1.address);
      expect(slaData.uptimeTarget).to.equal(UPTIME);
      expect(slaData.responseTimeMs).to.equal(RESPONSE);
      expect(slaData.accuracyTarget).to.equal(ACCURACY);
      expect(slaData.stakedAmount).to.equal(STAKE);
      expect(slaData.active).to.be.true;
    });

    it("Should reject zero stake", async function () {
      await expect(sla.connect(agent1).createSLA(UPTIME, RESPONSE, ACCURACY, PENALTY_RATE, DURATION)).to.be.reverted;
    });

    it("Should reject invalid uptime target", async function () {
      await expect(sla.connect(agent1).createSLA(4000, RESPONSE, ACCURACY, PENALTY_RATE, DURATION, { value: STAKE })).to.be.reverted;
    });

    it("Should reject invalid response time", async function () {
      await expect(sla.connect(agent1).createSLA(UPTIME, 70000, ACCURACY, PENALTY_RATE, DURATION, { value: STAKE })).to.be.reverted;
    });

    it("Should reject unregistered agent", async function () {
      await expect(sla.connect(random).createSLA(UPTIME, RESPONSE, ACCURACY, PENALTY_RATE, DURATION, { value: STAKE })).to.be.reverted;
    });

    it("Should reject invalid duration", async function () {
      await expect(sla.connect(agent1).createSLA(UPTIME, RESPONSE, ACCURACY, PENALTY_RATE, 100, { value: STAKE })).to.be.reverted;
    });
  });

  describe("Performance Reporting", function () {
    beforeEach(async function () {
      await sla.connect(agent1).createSLA(UPTIME, RESPONSE, ACCURACY, PENALTY_RATE, DURATION, { value: STAKE });
    });

    it("Should submit clean report (no violations)", async function () {
      const now = await time.latest();
      const tx = await sla.connect(monitor).submitReport(
        0, 9990, 500, 9900, now - 3600, now
      );
      await expect(tx).to.emit(sla, "SLAReportSubmitted");

      const report = await sla.reports(0);
      expect(report.uptimeViolation).to.be.false;
      expect(report.responseTimeViolation).to.be.false;
      expect(report.accuracyViolation).to.be.false;
      expect(report.penaltyApplied).to.equal(0);
    });

    it("Should detect uptime violation", async function () {
      const now = await time.latest();
      await sla.connect(monitor).submitReport(
        0, 9000, 500, 9900, now - 3600, now // Uptime 90% < 99.5%
      );

      const report = await sla.reports(0);
      expect(report.uptimeViolation).to.be.true;
      expect(report.penaltyApplied).to.be.gt(0);
    });

    it("Should detect response time violation", async function () {
      const now = await time.latest();
      await sla.connect(monitor).submitReport(
        0, 9990, 2000, 9900, now - 3600, now // Response 2000ms > 1000ms
      );

      const report = await sla.reports(0);
      expect(report.responseTimeViolation).to.be.true;
    });

    it("Should detect accuracy violation", async function () {
      const now = await time.latest();
      await sla.connect(monitor).submitReport(
        0, 9990, 500, 9000, now - 3600, now // Accuracy 90% < 98%
      );

      const report = await sla.reports(0);
      expect(report.accuracyViolation).to.be.true;
    });

    it("Should apply penalty and track violation count", async function () {
      const now = await time.latest();
      await sla.connect(monitor).submitReport(
        0, 9000, 2000, 9000, now - 3600, now // All three violated
      );

      const slaData = await sla.slas(0);
      expect(slaData.totalViolations).to.equal(3); // 3 violations
      expect(slaData.totalPenalties).to.be.gt(0);
    });

    it("Should reject unauthorized monitor", async function () {
      const now = await time.latest();
      await expect(sla.connect(random).submitReport(
        0, 9990, 500, 9900, now - 3600, now
      )).to.be.reverted;
    });

    it("Should reject report on expired SLA", async function () {
      await time.increase(DURATION + 1);
      const now = await time.latest();
      await expect(sla.connect(monitor).submitReport(
        0, 9990, 500, 9900, now - 7200, now - 3600
      )).to.be.reverted;
    });

    it("Should reject report period too short", async function () {
      const now = await time.latest();
      await expect(sla.connect(monitor).submitReport(
        0, 9990, 500, 9900, now - 60, now // Only 60 seconds
      )).to.be.reverted;
    });
  });

  describe("SLA Expiry & Stake Return", function () {
    it("Should return remaining stake on expiry", async function () {
      await sla.connect(agent1).createSLA(UPTIME, RESPONSE, ACCURACY, PENALTY_RATE, DURATION, { value: STAKE });

      await time.increase(DURATION + 1);

      const balBefore = await ethers.provider.getBalance(agent1.address);
      await sla.expireSLA(0);
      const balAfter = await ethers.provider.getBalance(agent1.address);

      expect(balAfter).to.be.gt(balBefore);

      const slaData = await sla.slas(0);
      expect(slaData.active).to.be.false;
    });

    it("Should reject early expiry", async function () {
      await sla.connect(agent1).createSLA(UPTIME, RESPONSE, ACCURACY, PENALTY_RATE, DURATION, { value: STAKE });
      await expect(sla.expireSLA(0)).to.be.reverted;
    });
  });

  describe("View Functions", function () {
    it("Should return agent performance", async function () {
      await sla.connect(agent1).createSLA(UPTIME, RESPONSE, ACCURACY, PENALTY_RATE, DURATION, { value: STAKE });
      const [active, staked, penalized, violations] = await sla.getAgentPerformance(agent1.address);
      expect(active).to.equal(1);
      expect(staked).to.equal(STAKE);
      expect(penalized).to.equal(0);
      expect(violations).to.equal(0);
    });

    it("Should return SLA stats", async function () {
      const [total, reports_, violations_, penalties_, pool_] = await sla.getSLAStats();
      expect(total).to.equal(0);
    });
  });

  describe("Admin", function () {
    it("Should manage monitors", async function () {
      await sla.setMonitor(random.address, true);
      expect(await sla.monitors(random.address)).to.be.true;
    });

    it("Should withdraw penalties", async function () {
      // Create SLA, submit violation report to generate penalties
      await sla.connect(agent1).createSLA(UPTIME, RESPONSE, ACCURACY, PENALTY_RATE, DURATION, { value: STAKE });
      const now = await time.latest();
      await sla.connect(monitor).submitReport(0, 5000, 5000, 5000, now - 3600, now);

      const poolBal = await sla.penaltyPool();
      if (poolBal > 0n) {
        await sla.withdrawPenalties(owner.address, poolBal);
        expect(await sla.penaltyPool()).to.equal(0);
      }
    });

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