import { expect } from "chai";
import { ethers } from "hardhat";
import { time } from "@nomicfoundation/hardhat-network-helpers";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";

/**
 * ═══════════════════════════════════════════════════════════════════════
 * FULL STACK INTEGRATION TEST — v1 + v2 Contracts Working Together
 * ═══════════════════════════════════════════════════════════════════════
 *
 * Verifies that ALL Vaultfire contracts (existing v1 + new v2) work
 * seamlessly together on a single chain. This simulates exactly what
 * will happen on Base, Avalanche, Arbitrum, and Polygon.
 *
 * v1 (existing):
 *   - MockIdentityRegistry (stands in for live ERC-8004 Identity)
 *   - MockBondsRegistry (stands in for live Accountability/Partnership Bonds)
 *   - MockReputationRegistry (stands in for live Reputation)
 *   - KeyVault — spending policy enforcement
 *   - VKPFactory — vault factory
 *   - DelegationRegistry — encrypted key delegation
 *   - SessionKeyManager — ephemeral session keys
 *
 * v2 (new — Agent Protection Layer):
 *   - AgentInsurancePool — pooled coverage
 *   - AgentSafetyManager — kill switch / safe mode
 *   - ComplianceRegistry — EU AI Act attestations
 *   - ReputationDecay — time-based trust decay
 *   - ScopedDelegation — ERC-7710-compatible delegation
 *   - AgentSLAEnforcer — on-chain SLA commitments
 */
describe("Full Stack Integration — v1 + v2 Together", function () {
  // Shared registries (mock the live ones)
  let identity: any;
  let bonds: any;
  let reputation: any;

  // v1 contracts
  let vkpFactory: any;
  let delegationReg: any;
  let sessionKeyMgr: any;

  // v2 contracts
  let insurance: any;
  let safety: any;
  let compliance: any;
  let decay: any;
  let scopedDelegation: any;
  let sla: any;

  // Signers
  let owner: SignerWithAddress;
  let agentA: SignerWithAddress;
  let agentB: SignerWithAddress;
  let agentC: SignerWithAddress;
  let auditor: SignerWithAddress;
  let monitor: SignerWithAddress;
  let sessionKey: SignerWithAddress;
  let claimant: SignerWithAddress;

  beforeEach(async function () {
    [owner, agentA, agentB, agentC, auditor, monitor, sessionKey, claimant] =
      await ethers.getSigners();

    // ───── Deploy shared registries (simulating live contracts) ─────
    const IdFactory = await ethers.getContractFactory("MockIdentityRegistry");
    identity = await IdFactory.deploy();

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

    const RepFactory = await ethers.getContractFactory("MockReputationRegistry");
    reputation = await RepFactory.deploy();

    // Register agents (simulating existing on-chain registrations)
    await identity.register(agentA.address);
    await identity.register(agentB.address);
    await identity.register(agentC.address);

    // Set reputations
    await reputation.setReputation(agentA.address, 90, 200);  // Top tier
    await reputation.setReputation(agentB.address, 55, 80);   // Mid tier
    await reputation.setReputation(agentC.address, 25, 15);   // Low tier

    // Create bonds
    await bonds.createBond(agentA.address, ethers.parseEther("5"));

    const idAddr = await identity.getAddress();
    const bondsAddr = await bonds.getAddress();
    const repAddr = await reputation.getAddress();

    // ───── Deploy v1 contracts ─────
    const VKPFactoryContract = await ethers.getContractFactory("VKPFactory");
    vkpFactory = await VKPFactoryContract.deploy(idAddr);

    const DelegationRegContract = await ethers.getContractFactory("DelegationRegistry");
    delegationReg = await DelegationRegContract.deploy(idAddr, bondsAddr, repAddr);

    const SessionContract = await ethers.getContractFactory("SessionKeyManager");
    sessionKeyMgr = await SessionContract.deploy(idAddr);

    // ───── Deploy v2 contracts ─────
    const InsFactory = await ethers.getContractFactory("AgentInsurancePool");
    insurance = await InsFactory.deploy(idAddr, bondsAddr, repAddr);

    const SafeFactory = await ethers.getContractFactory("AgentSafetyManager");
    safety = await SafeFactory.deploy(idAddr, bondsAddr, repAddr);

    const CompFactory = await ethers.getContractFactory("ComplianceRegistry");
    compliance = await CompFactory.deploy(idAddr);

    const DecFactory = await ethers.getContractFactory("ReputationDecay");
    decay = await DecFactory.deploy(idAddr, repAddr);

    const ScopeFactory = await ethers.getContractFactory("ScopedDelegation");
    scopedDelegation = await ScopeFactory.deploy(idAddr, bondsAddr, repAddr);

    const SLAFactory = await ethers.getContractFactory("AgentSLAEnforcer");
    sla = await SLAFactory.deploy(idAddr, bondsAddr, repAddr);

    // ───── Configure permissions ─────
    await compliance.authorizeAuditor(auditor.address, "ipfs://auditor-creds");
    await sla.setMonitor(monitor.address, true);
    await decay.setReporter(owner.address, true);
    await safety.setSafetyOperator(monitor.address, true);
  });

  // ═══════════════════════════════════════════════════
  // TEST 1: v1 and v2 share the same identity registry
  // ═══════════════════════════════════════════════════

  describe("Shared Registry Integration", function () {
    it("All 9 contracts resolve agent identity from the same registry", async function () {
      // v1: VKPFactory can create vault for registered agent
      const vaultAddr = await vkpFactory.connect(agentA).createVault.staticCall(
        ethers.parseEther("1"),
        ethers.parseEther("5"),
        ethers.parseEther("10"),
        ethers.parseEther("100")
      );
      await vkpFactory.connect(agentA).createVault(
        ethers.parseEther("1"),
        ethers.parseEther("5"),
        ethers.parseEther("10"),
        ethers.parseEther("100")
      );
      expect(vaultAddr).to.not.equal(ethers.ZeroAddress);

      // v1: SessionKeyManager creates session for registered agent
      await sessionKeyMgr.connect(agentA).createSession(
        sessionKey.address,
        ethers.parseEther("0.5"),
        ethers.parseEther("5"),
        86400, // 1 day
        []     // empty allowlist = allow any contract
      );
      expect(await sessionKeyMgr.sessionCount()).to.equal(1);

      // v1: DelegationRegistry creates delegation for registered agents
      await delegationReg.connect(agentA).createDelegation(
        agentB.address,
        ethers.parseEther("1"),
        ethers.parseEther("10"),
        7 * 86400,
        0, 0,
        [], [], // empty allowlists
        ethers.keccak256(ethers.toUtf8Bytes("encrypted-key"))
      );
      expect(await delegationReg.delegationCount()).to.equal(1);

      // v2: Insurance reads reputation for same agent
      const [premium, tier] = await insurance.getPremiumQuote(agentA.address, ethers.parseEther("10"), 90 * 86400);
      expect(tier).to.equal(1); // Low risk (rep 90)

      // v2: Safety can check the same agent
      expect(await safety.isAgentOperational(agentA.address)).to.be.true;

      // v2: Compliance can attest the same agent
      await compliance.connect(auditor).issueAttestation(
        agentA.address, 1, 365 * 86400, "ipfs://doc", "EU_AI_ACT_2026",
        true, true, true, true
      );
      expect(await compliance.isCompliant(agentA.address)).to.be.true;

      // v2: Decay reads base reputation for same agent
      await decay.initializeProfile(agentA.address);
      const [dScore, bScore] = await decay.getCurrentScore(agentA.address);
      expect(bScore).to.equal(90);

      // v2: ScopedDelegation creates scope for same agents
      await scopedDelegation.connect(agentA).createScope(
        agentB.address, ethers.parseEther("1"), ethers.parseEther("10"),
        100, 60, 90 * 86400
      );
      expect(await scopedDelegation.totalScopes()).to.equal(1);

      // v2: SLA enforcer creates SLA for same agent
      await sla.connect(agentA).createSLA(
        9500, 5000, 9800, 500, 30 * 86400,
        { value: ethers.parseEther("0.5") }
      );
      expect(await sla.totalSLAs()).to.equal(1);
    });

    it("Unregistered agents are blocked by ALL contracts consistently", async function () {
      const unregistered = claimant; // Not registered as agent

      // v1: VKPFactory rejects
      await expect(
        vkpFactory.connect(unregistered).createVault(
          ethers.parseEther("1"), ethers.parseEther("5"),
          ethers.parseEther("10"), ethers.parseEther("100")
        )
      ).to.be.reverted;

      // v1: SessionKeyManager rejects
      await expect(
        sessionKeyMgr.connect(unregistered).createSession(
          sessionKey.address, ethers.parseEther("0.5"),
          ethers.parseEther("5"), 86400, []
        )
      ).to.be.reverted;

      // v2: Insurance rejects
      await expect(
        insurance.connect(unregistered).createPolicy(
          ethers.parseEther("5"), 90 * 86400,
          { value: ethers.parseEther("1") }
        )
      ).to.be.revertedWith("AgentInsurancePool: agent not registered or inactive");

      // v2: SLA rejects
      await expect(
        sla.connect(unregistered).createSLA(
          9500, 5000, 9800, 500, 30 * 86400,
          { value: ethers.parseEther("0.5") }
        )
      ).to.be.revertedWith("AgentSLAEnforcer: agent not registered or inactive");

      // v2: ScopedDelegation rejects
      await expect(
        scopedDelegation.connect(unregistered).createScope(
          agentA.address, ethers.parseEther("1"), ethers.parseEther("10"),
          100, 60, 90 * 86400
        )
      ).to.be.revertedWith("ScopedDelegation: agent not registered or inactive");
    });
  });

  // ═══════════════════════════════════════════════════
  // TEST 2: Full Agent Lifecycle — v1 + v2 Together
  // ═══════════════════════════════════════════════════

  describe("Complete Agent Lifecycle — All Contracts", function () {
    it("Agent onboards with v1 + v2 infrastructure, operates, gets penalized, recovers", async function () {
      // ── PHASE 1: ONBOARDING (v1 + v2) ──

      // v1: Create KeyVault
      await vkpFactory.connect(agentA).createVault(
        ethers.parseEther("1"), ethers.parseEther("5"),
        ethers.parseEther("20"), ethers.parseEther("100")
      );
      const vaults = await vkpFactory.getAgentVaults(agentA.address);
      expect(vaults.length).to.equal(1);

      // v1: Create session key
      await sessionKeyMgr.connect(agentA).createSession(
        sessionKey.address, ethers.parseEther("0.5"),
        ethers.parseEther("5"), 86400, []
      );

      // v1: Delegate to agent B
      await delegationReg.connect(agentA).createDelegation(
        agentB.address, ethers.parseEther("1"), ethers.parseEther("10"),
        30 * 86400, 0, 0,
        [], [], // empty allowlists
        ethers.keccak256(ethers.toUtf8Bytes("encrypted-key"))
      );

      // v2: Compliance attestation
      await compliance.connect(auditor).issueAttestation(
        agentA.address, 1, 365 * 86400, "ipfs://compliance-A", "EU_AI_ACT_2026",
        true, true, true, true
      );

      // v2: Insurance policy (use high coverage so premium covers claim tests)
      const [premium] = await insurance.getPremiumQuote(agentA.address, ethers.parseEther("100"), 365 * 86400);
      await insurance.connect(agentA).createPolicy(ethers.parseEther("100"), 365 * 86400, { value: premium });

      // v2: SLA commitment
      await sla.connect(agentA).createSLA(
        9500, 5000, 9800, 500, 30 * 86400,
        { value: ethers.parseEther("1") }
      );

      // v2: Reputation decay profile
      await decay.initializeProfile(agentA.address);

      // v2: Scoped delegation
      await scopedDelegation.connect(agentA).createScope(
        agentB.address, ethers.parseEther("0.5"), ethers.parseEther("5"),
        50, 120, 60 * 86400
      );

      // Verify all systems active
      expect(await safety.isAgentOperational(agentA.address)).to.be.true;
      expect(await compliance.isCompliant(agentA.address)).to.be.true;
      expect(await insurance.hasActivePolicy(agentA.address)).to.be.true;

      // ── PHASE 2: NORMAL OPERATION ──

      // v2: Good SLA report
      let now = await time.latest();
      await sla.connect(monitor).submitReport(
        0, 9900, 3000, 9900, now - 7200, now - 3600
      );

      // v2: Positive reputation activity
      await decay.connect(owner).recordActivity(agentA.address, "tasks", true, 8);
      let [dScore] = await decay.getCurrentScore(agentA.address);
      expect(dScore).to.equal(98); // 90 + 8

      // ── PHASE 3: SLA VIOLATION ──

      now = await time.latest();
      await sla.connect(monitor).submitReport(
        0, 7000, 15000, 7000, now - 7200, now - 3600
      );

      // v2: Negative reputation activity
      await decay.connect(owner).recordActivity(agentA.address, "tasks", false, 6);
      [dScore] = await decay.getCurrentScore(agentA.address);
      expect(dScore).to.equal(86); // 98 - (6*2) = 86

      // ── PHASE 4: INSURANCE CLAIM ──
      // Claim must be <= pool balance
      const poolStats = await insurance.getPoolStats();
      const claimAmt = poolStats[0] / 2n;
      await insurance.connect(claimant).fileClaim(
        0, claimAmt, "ipfs://damage-from-agent-failure"
      );
      await insurance.connect(owner).resolveClaim(0, true);

      const [, , , paidOut] = await insurance.getPoolStats();
      expect(paidOut).to.equal(claimAmt);

      // ── PHASE 5: RECOVERY ──

      // Positive activity rebuilds trust
      await decay.connect(owner).recordActivity(agentA.address, "tasks", true, 10);
      [dScore] = await decay.getCurrentScore(agentA.address);
      expect(dScore).to.equal(96); // 86 + 10

      // Agent still fully operational across ALL systems
      expect(await safety.isAgentOperational(agentA.address)).to.be.true;
      expect(await compliance.isCompliant(agentA.address)).to.be.true;
    });
  });

  // ═══════════════════════════════════════════════════
  // TEST 3: KeyVault + ScopedDelegation interaction
  // ═══════════════════════════════════════════════════

  describe("KeyVault + ScopedDelegation Synergy", function () {
    it("ScopedDelegation limits align with KeyVault spending policies", async function () {
      // Create a KeyVault with spending limits
      await vkpFactory.connect(agentA).createVault(
        ethers.parseEther("1"),   // 1 ETH per tx
        ethers.parseEther("5"),   // 5 ETH per hour
        ethers.parseEther("20"),  // 20 ETH per day
        ethers.parseEther("100")  // Emergency at 100 ETH
      );

      // ScopedDelegation limits should be WITHIN vault limits
      await scopedDelegation.connect(agentA).createScope(
        agentB.address,
        ethers.parseEther("0.5"),  // 0.5 ETH per tx (within vault's 1 ETH)
        ethers.parseEther("3"),    // 3 ETH total (within vault's 5 ETH/hr)
        10,  // 10 tx max
        300, // 5 min rate limit
        30 * 86400
      );

      // Use the scope
      await scopedDelegation.connect(agentB).useScope(
        0, ethers.parseEther("0.5"), ethers.ZeroAddress, "0x00000000"
      );

      const [remaining] = await scopedDelegation.getScopeRemaining(0);
      expect(remaining).to.equal(ethers.parseEther("2.5")); // 3 - 0.5
    });
  });

  // ═══════════════════════════════════════════════════
  // TEST 4: Safety Manager affects all operations
  // ═══════════════════════════════════════════════════

  describe("Safety Manager — Cross-Contract Impact", function () {
    it("Frozen agent's status is queryable by any contract checking safety", async function () {
      // Freeze agent B
      await safety.connect(owner).freezeAgent(agentB.address, "Security incident");

      // Safety state is on-chain and queryable
      expect(await safety.isAgentFrozen(agentB.address)).to.be.true;
      expect(await safety.isAgentOperational(agentB.address)).to.be.false;
      expect(await safety.isAgentInSafeMode(agentB.address)).to.be.false;

      // Get the full audit trail
      const [status, changedAt, changedBy, reason] = await safety.getAgentStatus(agentB.address);
      expect(status).to.equal(2); // Frozen
      expect(changedBy).to.equal(owner.address);

      // Reactivate
      await safety.connect(owner).reactivateAgent(agentB.address, "Cleared");
      expect(await safety.isAgentOperational(agentB.address)).to.be.true;
    });
  });

  // ═══════════════════════════════════════════════════
  // TEST 5: Multi-agent ecosystem
  // ═══════════════════════════════════════════════════

  describe("Multi-Agent Ecosystem — 3 Agents Interacting", function () {
    it("Three agents with different trust levels get different treatment", async function () {
      // Agent A: High trust (90) → Low risk tier, lowest premium
      const [premiumA, tierA] = await insurance.getPremiumQuote(agentA.address, ethers.parseEther("10"), 90 * 86400);
      expect(tierA).to.equal(1);

      // Agent B: Mid trust (55) → Medium risk tier, higher premium
      const [premiumB, tierB] = await insurance.getPremiumQuote(agentB.address, ethers.parseEther("10"), 90 * 86400);
      expect(tierB).to.equal(2);

      // Agent C: Low trust (25) → High risk tier, highest premium
      const [premiumC, tierC] = await insurance.getPremiumQuote(agentC.address, ethers.parseEther("10"), 90 * 86400);
      expect(tierC).to.equal(3);

      // Premium ordering is correct
      expect(premiumA).to.be.lt(premiumB);
      expect(premiumB).to.be.lt(premiumC);

      // All three can create policies at their respective rates
      await insurance.connect(agentA).createPolicy(ethers.parseEther("10"), 90 * 86400, { value: premiumA });
      await insurance.connect(agentB).createPolicy(ethers.parseEther("10"), 90 * 86400, { value: premiumB });
      await insurance.connect(agentC).createPolicy(ethers.parseEther("10"), 90 * 86400, { value: premiumC });

      // Verify pool has all 3 premiums
      const [poolBal, policyCount] = await insurance.getPoolStats();
      expect(policyCount).to.equal(3);
      expect(poolBal).to.equal(premiumA + premiumB + premiumC);

      // Agent A delegates to B with generous limits
      await scopedDelegation.connect(agentA).createScope(
        agentB.address, ethers.parseEther("1"), ethers.parseEther("10"),
        100, 60, 90 * 86400
      );

      // Agent B sub-delegates to C with tighter limits
      await scopedDelegation.connect(agentB).subDelegate(
        0, agentC.address,
        ethers.parseEther("0.1"), ethers.parseEther("1"),
        10, 300, 7 * 86400
      );

      // All 3 agents have SLAs
      await sla.connect(agentA).createSLA(9900, 2000, 9900, 300, 30 * 86400, { value: ethers.parseEther("2") });
      await sla.connect(agentB).createSLA(9500, 5000, 9500, 500, 30 * 86400, { value: ethers.parseEther("1") });
      await sla.connect(agentC).createSLA(9000, 10000, 9000, 800, 14 * 86400, { value: ethers.parseEther("0.5") });

      expect(await sla.totalSLAs()).to.equal(3);

      // All 3 have decay profiles
      await decay.initializeProfile(agentA.address);
      await decay.initializeProfile(agentB.address);
      await decay.initializeProfile(agentC.address);

      expect(await decay.totalProfiles()).to.equal(3);
    });
  });

  // ═══════════════════════════════════════════════════
  // TEST 6: Edge case — unregister agent mid-lifecycle
  // ═══════════════════════════════════════════════════

  describe("Edge Cases", function () {
    it("Insurance pool balance accounting is always correct", async function () {
      // 2 agents create policies with high coverage for adequate pool
      const [premA] = await insurance.getPremiumQuote(agentA.address, ethers.parseEther("100"), 365 * 86400);
      const [premB] = await insurance.getPremiumQuote(agentB.address, ethers.parseEther("100"), 365 * 86400);
      await insurance.connect(agentA).createPolicy(ethers.parseEther("100"), 365 * 86400, { value: premA });
      await insurance.connect(agentB).createPolicy(ethers.parseEther("100"), 365 * 86400, { value: premB });

      const expectedPool = premA + premB;
      let [poolBal] = await insurance.getPoolStats();
      expect(poolBal).to.equal(expectedPool);

      // File and pay claim — claim half of first policy's premium
      const claimAmt = premA / 2n;
      await insurance.connect(claimant).fileClaim(0, claimAmt, "ipfs://evidence");
      await insurance.connect(owner).resolveClaim(0, true);

      [poolBal] = await insurance.getPoolStats();
      expect(poolBal).to.equal(expectedPool - claimAmt);

      // Contract balance matches
      const contractBal = await ethers.provider.getBalance(await insurance.getAddress());
      expect(contractBal).to.equal(expectedPool - claimAmt);
    });

    it("SLA penalty pool balance accounting is always correct", async function () {
      await sla.connect(agentA).createSLA(
        9900, 1000, 9900, 1000, 30 * 86400,
        { value: ethers.parseEther("5") }
      );

      const now = await time.latest();
      // All 3 violated: penalty = 5 * 1000 * 3 / 10000 = 1.5 ETH
      await sla.connect(monitor).submitReport(0, 1000, 50000, 1000, now - 7200, now - 3600);

      const penaltyPool = await sla.penaltyPool();
      expect(penaltyPool).to.equal(ethers.parseEther("1.5"));

      // Contract balance = 5 ETH (staked, still held)
      const contractBal = await ethers.provider.getBalance(await sla.getAddress());
      expect(contractBal).to.equal(ethers.parseEther("5"));
    });

    it("Delegation scope revocation works properly", async function () {
      await scopedDelegation.connect(agentA).createScope(
        agentB.address, ethers.parseEther("1"), ethers.parseEther("10"),
        100, 0, 90 * 86400
      );

      expect(await scopedDelegation.isScopeValid(0)).to.be.true;

      // Revoke
      await scopedDelegation.connect(agentA).revokeScope(0);
      expect(await scopedDelegation.isScopeValid(0)).to.be.false;

      // Cannot use revoked scope
      await expect(
        scopedDelegation.connect(agentB).useScope(0, ethers.parseEther("0.1"), ethers.ZeroAddress, "0x00000000")
      ).to.be.revertedWith("ScopedDelegation: scope not active");
    });
  });
});
