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

describe("AgentInsurancePool", function () {
  let identity: MockIdentityRegistry;
  let bonds: MockBondsRegistry;
  let reputation: MockReputationRegistry;
  let pool: AgentInsurancePool;
  let owner: SignerWithAddress;
  let agent1: SignerWithAddress;
  let agent2: SignerWithAddress;
  let claimant: SignerWithAddress;
  let random: SignerWithAddress;

  const COVERAGE = ethers.parseEther("10");
  const DURATION = 90 * 24 * 3600; // 90 days

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

    const IdentityFactory = await ethers.getContractFactory("MockIdentityRegistry");
    identity = (await IdentityFactory.deploy()) as MockIdentityRegistry;
    await identity.register(agent1.address);
    await identity.register(agent2.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); // High rep = low risk
    await reputation.setReputation(agent2.address, 30, 20); // Low rep = high risk

    const PoolFactory = await ethers.getContractFactory("AgentInsurancePool");
    pool = (await PoolFactory.deploy(
      await identity.getAddress(),
      await bonds.getAddress(),
      await reputation.getAddress()
    )) as AgentInsurancePool;
  });

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

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

  describe("Policy Creation", function () {
    it("Should create policy with correct risk tier (low risk)", async function () {
      const [premium] = await pool.getPremiumQuote(agent1.address, COVERAGE, DURATION);
      const tx = await pool.connect(agent1).createPolicy(COVERAGE, DURATION, { value: premium });
      await expect(tx).to.emit(pool, "PolicyCreated");

      const policy = await pool.policies(0);
      expect(policy.agent).to.equal(agent1.address);
      expect(policy.coverageLimit).to.equal(COVERAGE);
      expect(policy.riskTier).to.equal(1n); // Low risk (rep 80)
      expect(policy.active).to.be.true;
    });

    it("Should charge higher premium for high risk agents", async function () {
      const [premiumLow] = await pool.getPremiumQuote(agent1.address, COVERAGE, DURATION);
      const [premiumHigh] = await pool.getPremiumQuote(agent2.address, COVERAGE, DURATION);
      expect(premiumHigh).to.be.gt(premiumLow);
    });

    it("Should reject unregistered agents", async function () {
      const [premium] = await pool.getPremiumQuote(agent1.address, COVERAGE, DURATION);
      await expect(pool.connect(random).createPolicy(COVERAGE, DURATION, { value: premium })).to.be.reverted;
    });

    it("Should reject insufficient premium", async function () {
      await expect(pool.connect(agent1).createPolicy(COVERAGE, DURATION, { value: 1 })).to.be.reverted;
    });

    it("Should reject duplicate active policy", async function () {
      const [premium] = await pool.getPremiumQuote(agent1.address, COVERAGE, DURATION);
      await pool.connect(agent1).createPolicy(COVERAGE, DURATION, { value: premium });
      await expect(pool.connect(agent1).createPolicy(COVERAGE, DURATION, { value: premium })).to.be.reverted;
    });

    it("Should reject zero coverage", async function () {
      await expect(pool.connect(agent1).createPolicy(0, DURATION, { value: ethers.parseEther("1") })).to.be.reverted;
    });

    it("Should reject invalid duration", async function () {
      const [premium] = await pool.getPremiumQuote(agent1.address, COVERAGE, 1);
      await expect(pool.connect(agent1).createPolicy(COVERAGE, 1, { value: premium })).to.be.reverted;
    });
  });

  describe("Claims", function () {
    beforeEach(async function () {
      const [premium] = await pool.getPremiumQuote(agent1.address, COVERAGE, DURATION);
      await pool.connect(agent1).createPolicy(COVERAGE, DURATION, { value: premium });
    });

    it("Should file a claim", async function () {
      const tx = await pool.connect(claimant).fileClaim(0, ethers.parseEther("1"), "ipfs://evidence");
      await expect(tx).to.emit(pool, "ClaimFiled");

      const claim = await pool.claims(0);
      expect(claim.claimant).to.equal(claimant.address);
      expect(claim.amount).to.equal(ethers.parseEther("1"));
      expect(claim.status).to.equal(0); // Pending
    });

    it("Should approve and pay a claim", async function () {
      await pool.connect(claimant).fileClaim(0, ethers.parseEther("0.01"), "ipfs://evidence");

      const balBefore = await ethers.provider.getBalance(claimant.address);
      const tx = await pool.resolveClaim(0, true);
      await expect(tx).to.emit(pool, "ClaimResolved");

      const claim = await pool.claims(0);
      expect(claim.status).to.equal(3); // Paid

      const balAfter = await ethers.provider.getBalance(claimant.address);
      expect(balAfter).to.be.gt(balBefore);
    });

    it("Should deny a claim", async function () {
      await pool.connect(claimant).fileClaim(0, ethers.parseEther("1"), "ipfs://evidence");
      await pool.resolveClaim(0, false);
      const claim = await pool.claims(0);
      expect(claim.status).to.equal(2); // Denied
    });

    it("Should reject claim exceeding coverage", async function () {
      await expect(pool.connect(claimant).fileClaim(0, COVERAGE + 1n, "ipfs://evidence")).to.be.reverted;
    });

    it("Should reject claim on inactive policy", async function () {
      await time.increase(DURATION + 1);
      await pool.expirePolicy(0);
      await expect(pool.connect(claimant).fileClaim(0, ethers.parseEther("1"), "ipfs://evidence")).to.be.reverted;
    });

    it("Should only allow arbiter to resolve claims", async function () {
      await pool.connect(claimant).fileClaim(0, ethers.parseEther("1"), "ipfs://evidence");
      await expect(pool.connect(random).resolveClaim(0, true)).to.be.reverted;
    });
  });

  describe("Policy Expiry", function () {
    it("Should expire policy after duration", async function () {
      const [premium] = await pool.getPremiumQuote(agent1.address, COVERAGE, DURATION);
      await pool.connect(agent1).createPolicy(COVERAGE, DURATION, { value: premium });

      await time.increase(DURATION + 1);
      await pool.expirePolicy(0);

      const policy = await pool.policies(0);
      expect(policy.active).to.be.false;
      expect(await pool.hasActivePolicy(agent1.address)).to.be.false;
    });

    it("Should reject early expiry", async function () {
      const [premium] = await pool.getPremiumQuote(agent1.address, COVERAGE, DURATION);
      await pool.connect(agent1).createPolicy(COVERAGE, DURATION, { value: premium });
      await expect(pool.expirePolicy(0)).to.be.reverted;
    });
  });

  describe("Admin", function () {
    it("Should update arbiter", async function () {
      await pool.setArbiter(agent1.address);
      expect(await pool.arbiter()).to.equal(agent1.address);
    });

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

    it("Should reject non-owner admin calls", async function () {
      await expect(pool.connect(random).setArbiter(random.address)).to.be.reverted;
      await expect(pool.connect(random).transferOwnership(random.address)).to.be.reverted;
    });
  });

  describe("View Functions", function () {
    it("Should return pool stats", async function () {
      const [balance, policyCount, claimCount, paidOut] = await pool.getPoolStats();
      expect(balance).to.equal(0);
      expect(policyCount).to.equal(0);
    });

    it("Should return agent policies", async function () {
      const policies = await pool.getAgentPolicies(agent1.address);
      expect(policies.length).to.equal(0);
    });
  });
});
