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

describe("ReputationDecay", function () {
  let identity: MockIdentityRegistry;
  let reputation: MockReputationRegistry;
  let decay: ReputationDecay;
  let owner: SignerWithAddress;
  let agent1: SignerWithAddress;
  let reporter: SignerWithAddress;
  let random: SignerWithAddress;

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

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

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

    const DecayFactory = await ethers.getContractFactory("ReputationDecay");
    decay = (await DecayFactory.deploy(
      await identity.getAddress(),
      await reputation.getAddress()
    )) as ReputationDecay;

    // Authorize reporter
    await decay.setReporter(reporter.address, true);
  });

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

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

  describe("Profile Initialization", function () {
    it("Should initialize profile from reputation registry", async function () {
      const tx = await decay.initializeProfile(agent1.address);
      await expect(tx).to.emit(decay, "ProfileCreated");

      const profile = await decay.profiles(agent1.address);
      expect(profile.baseScore).to.equal(75);
      expect(profile.decayedScore).to.equal(75);
      expect(profile.exists).to.be.true;
    });

    it("Should reject unregistered agent", async function () {
      await expect(decay.initializeProfile(random.address)).to.be.reverted;
    });
  });

  describe("Activity Recording", function () {
    beforeEach(async function () {
      await decay.initializeProfile(agent1.address);
    });

    it("Should record positive activity and boost score", async function () {
      const tx = await decay.connect(reporter).recordActivity(agent1.address, "trading", true, 5);
      await expect(tx).to.emit(decay, "ActivityRecorded");

      const profile = await decay.profiles(agent1.address);
      expect(profile.decayedScore).to.equal(80); // 75 + 5
      expect(profile.positiveActivities).to.equal(1);
    });

    it("Should record negative activity and reduce score", async function () {
      await decay.connect(reporter).recordActivity(agent1.address, "trading", false, 5);
      const profile = await decay.profiles(agent1.address);
      expect(profile.decayedScore).to.equal(65); // 75 - (5*2)
      expect(profile.negativeActivities).to.equal(1);
    });

    it("Should cap score at 100", async function () {
      await reputation.setReputation(agent1.address, 95, 50);
      await decay.initializeProfile(agent1.address);
      await decay.connect(reporter).recordActivity(agent1.address, "trading", true, 10);
      const profile = await decay.profiles(agent1.address);
      expect(profile.decayedScore).to.equal(100);
    });

    it("Should floor score at minimum", async function () {
      await reputation.setReputation(agent1.address, 10, 5);
      await decay.initializeProfile(agent1.address);
      await decay.connect(reporter).recordActivity(agent1.address, "trading", false, 10);
      const profile = await decay.profiles(agent1.address);
      expect(profile.decayedScore).to.equal(5); // minDecayedScore
    });

    it("Should reject unauthorized reporter", async function () {
      await expect(decay.connect(random).recordActivity(agent1.address, "trading", true, 5)).to.be.reverted;
    });

    it("Should reject invalid weight", async function () {
      await expect(decay.connect(reporter).recordActivity(agent1.address, "trading", true, 0)).to.be.reverted;
      await expect(decay.connect(reporter).recordActivity(agent1.address, "trading", true, 11)).to.be.reverted;
    });

    it("Should reject empty context", async function () {
      await expect(decay.connect(reporter).recordActivity(agent1.address, "", true, 5)).to.be.reverted;
    });
  });

  describe("Context-Specific Reputation", function () {
    beforeEach(async function () {
      await decay.initializeProfile(agent1.address);
    });

    it("Should track separate context scores", async function () {
      await decay.connect(reporter).recordActivity(agent1.address, "trading", true, 8);
      await decay.connect(reporter).recordActivity(agent1.address, "code_review", false, 3);

      const [tradingScore] = await decay.getContextScore(agent1.address, "trading");
      const [codeScore] = await decay.getContextScore(agent1.address, "code_review");

      expect(tradingScore).to.equal(58); // 50 + 8
      expect(codeScore).to.equal(44); // 50 - 6
    });

    it("Should return zero for unknown context", async function () {
      const [score, activities] = await decay.getContextScore(agent1.address, "unknown");
      expect(score).to.equal(0);
      expect(activities).to.equal(0);
    });
  });

  describe("Decay Mechanism", function () {
    beforeEach(async function () {
      await decay.initializeProfile(agent1.address);
    });

    it("Should not decay within grace period", async function () {
      await time.increase(6 * 24 * 3600); // 6 days (within 7 day grace)
      await decay.applyDecay(agent1.address);

      const profile = await decay.profiles(agent1.address);
      expect(profile.decayedScore).to.equal(75); // No decay
    });

    it("Should decay after grace period", async function () {
      await time.increase(17 * 24 * 3600); // 17 days (7 grace + 10 decay days)
      await decay.applyDecay(agent1.address);

      const profile = await decay.profiles(agent1.address);
      expect(profile.decayedScore).to.equal(65); // 75 - 10 days * 1/day
    });

    it("Should not decay below minimum", async function () {
      await time.increase(200 * 24 * 3600); // 200 days
      await decay.applyDecay(agent1.address);

      const profile = await decay.profiles(agent1.address);
      expect(profile.decayedScore).to.equal(5); // minDecayedScore
    });

    it("Should read current score without modifying state", async function () {
      await time.increase(17 * 24 * 3600);

      const [decayed, base, days_, total] = await decay.getCurrentScore(agent1.address);
      expect(decayed).to.equal(65);
      expect(base).to.equal(75);

      // Verify state wasn't modified
      const profile = await decay.profiles(agent1.address);
      expect(profile.decayedScore).to.equal(75); // Still 75 in storage
    });
  });

  describe("Recovery Bonus", function () {
    it("Should give recovery bonus when coming from low score", async function () {
      // Set low reputation and initialize
      await reputation.setReputation(agent1.address, 20, 10);
      await decay.initializeProfile(agent1.address);

      // Record positive activity — should get recovery bonus
      await decay.connect(reporter).recordActivity(agent1.address, "trading", true, 5);
      const profile = await decay.profiles(agent1.address);
      // Score should be > 20 + 5 due to recovery bonus
      expect(profile.decayedScore).to.be.gt(25);
    });
  });

  describe("Admin", function () {
    it("Should update decay parameters", async function () {
      await decay.setDecayParameters(2, 14 * 24 * 3600, 10, 15);
      expect(await decay.decayRatePerDay()).to.equal(2);
      expect(await decay.decayGracePeriod()).to.equal(14 * 24 * 3600);
      expect(await decay.minDecayedScore()).to.equal(10);
    });

    it("Should reject invalid parameters", async function () {
      await expect(decay.setDecayParameters(0, 7 * 24 * 3600, 5, 10)).to.be.reverted; // rate 0
      await expect(decay.setDecayParameters(1, 100, 5, 10)).to.be.reverted; // grace too short
      await expect(decay.setDecayParameters(1, 7 * 24 * 3600, 25, 10)).to.be.reverted; // min too high
    });

    it("Should manage reporters", async function () {
      await decay.setReporter(random.address, true);
      expect(await decay.reporters(random.address)).to.be.true;
      await decay.setReporter(random.address, false);
      expect(await decay.reporters(random.address)).to.be.false;
    });
  });
});
