/**
 * Comprehensive Test Suite for Byzantine Consensus
 * Tests quorum configuration, fault tolerance, and consensus mechanisms
 */

import {
  ByzantineConsensus,
  Agent,
  ConsensusMessage,
  ConsensusProposal,
  ConsensusState,
} from "../byzantine-consensus.js";

describe("ByzantineConsensus", () => {
  let consensus: ByzantineConsensus;
  let testAgents: Agent[];

  beforeEach(() => {
    consensus = new ByzantineConsensus("test-agent", 4);

    // Setup test agents
    testAgents = [
      {
        id: "agent-1",
        publicKey: "key1",
        isLeader: false,
        reputation: 1.0,
        lastActiveTime: new Date(),
      },
      {
        id: "agent-2",
        publicKey: "key2",
        isLeader: false,
        reputation: 0.9,
        lastActiveTime: new Date(),
      },
      {
        id: "agent-3",
        publicKey: "key3",
        isLeader: false,
        reputation: 0.8,
        lastActiveTime: new Date(),
      },
      {
        id: "malicious-agent",
        publicKey: "key4",
        isLeader: false,
        isMalicious: true,
        reputation: 0.5,
        lastActiveTime: new Date(),
      },
    ];

    // Register agents
    testAgents.forEach((agent) => consensus.registerAgent(agent));
  });

  afterEach(() => {
    consensus?.removeAllListeners();
  });

  describe("Initialization and Configuration", () => {
    it("should initialize with correct quorum parameters", () => {
      const state = consensus.getState();

      expect(state.currentView).toBe(0);
      expect(state.sequenceNumber).toBe(0);
      expect(state.phase).toBe("pre-prepare");
      expect(state.activeAgents.size).toBe(4);
    });

    it("should calculate fault threshold correctly", () => {
      // For 4 agents, fault threshold should be floor((4-1)/3) = 1
      const consensus4 = new ByzantineConsensus("test", 4);
      expect(consensus4.canReachConsensus()).toBe(true);

      // For 7 agents, fault threshold should be floor((7-1)/3) = 2
      const consensus7 = new ByzantineConsensus("test", 7);
      expect(consensus7.canReachConsensus()).toBe(true);

      // For 10 agents, fault threshold should be floor((10-1)/3) = 3
      const consensus10 = new ByzantineConsensus("test", 10);
      expect(consensus10.canReachConsensus()).toBe(true);
    });

    it("should handle minimum node requirements for Byzantine fault tolerance", () => {
      // Need at least 3f + 1 nodes to tolerate f faults
      const consensus1 = new ByzantineConsensus("test", 1);
      expect(consensus1.canReachConsensus()).toBe(false);

      const consensus3 = new ByzantineConsensus("test", 3);
      expect(consensus3.canReachConsensus()).toBe(false); // No agents registered

      const consensus4 = new ByzantineConsensus("test", 4);
      // Register enough honest agents
      for (let i = 0; i < 4; i++) {
        consensus4.registerAgent({
          id: `agent-${i}`,
          publicKey: `key${i}`,
          isLeader: false,
          reputation: 1.0,
          lastActiveTime: new Date(),
        });
      }
      expect(consensus4.canReachConsensus()).toBe(true);
    });

    it("should select leader correctly using round-robin", () => {
      const state = consensus.getState();

      // Initial leader should be first agent (view 0 % 4 = 0)
      const activeAgents = Array.from(state.activeAgents);
      expect(state.leader).toBe(activeAgents[0]);
    });
  });

  describe("Quorum Configuration", () => {
    describe("Quorum Size Calculations", () => {
      it("should calculate correct quorum sizes for different network sizes", () => {
        const testCases = [
          { totalNodes: 4, expectedQuorum: 2, faultTolerance: 1 },
          { totalNodes: 7, expectedQuorum: 4, faultTolerance: 2 },
          { totalNodes: 10, expectedQuorum: 6, faultTolerance: 3 },
          { totalNodes: 13, expectedQuorum: 8, faultTolerance: 4 },
        ];

        testCases.forEach(({ totalNodes, expectedQuorum, faultTolerance }) => {
          const testConsensus = new ByzantineConsensus("test", totalNodes);

          // Quorum size is 2f for prepare/commit phases in PBFT
          const calculatedQuorum = 2 * Math.floor((totalNodes - 1) / 3);
          expect(calculatedQuorum).toBe(expectedQuorum);

          // Can tolerate up to f malicious nodes
          expect(Math.floor((totalNodes - 1) / 3)).toBe(faultTolerance);
        });
      });

      it("should validate quorum requirements for consensus phases", async () => {
        const proposal: ConsensusProposal = {
          id: "test-proposal",
          content: { data: "test" },
          proposerId: "agent-1",
          timestamp: new Date(),
          hash: "test-hash",
        };

        // Mock leader selection to make agent-1 the leader
        const state = consensus.getState();
        state.leader = "agent-1";

        let prepareCount = 0;
        let commitCount = 0;

        consensus.on("broadcast-message", (message: ConsensusMessage) => {
          if (message.type === "prepare") {
            prepareCount++;
          } else if (message.type === "commit") {
            commitCount++;
          }
        });

        // Start consensus - should require 2f responses (2 for 4 nodes)
        const result = await consensus.startConsensus(proposal);

        // In a real scenario, we'd need to simulate message handling
        // For now, just verify the structure is correct
        expect(result).toBeDefined();
      });

      it("should handle dynamic quorum adjustments", () => {
        // Start with 4 agents
        expect(consensus.canReachConsensus()).toBe(true);

        // Remove an agent - should still work with 3 honest agents
        consensus.removeAgent("malicious-agent");
        expect(consensus.canReachConsensus()).toBe(true);

        // Remove another agent - now only 2 agents, cannot reach consensus
        consensus.removeAgent("agent-3");
        expect(consensus.canReachConsensus()).toBe(false);
      });
    });

    describe("Fault Tolerance Validation", () => {
      it("should detect when network cannot tolerate faults", () => {
        // With 4 total agents and 1 malicious, should still work
        expect(consensus.canReachConsensus()).toBe(true);

        // Add more malicious agents
        consensus.registerAgent({
          id: "malicious-2",
          publicKey: "key5",
          isLeader: false,
          isMalicious: true,
          reputation: 0.3,
          lastActiveTime: new Date(),
        });

        // Now have 2 malicious out of 5 total - should fail
        expect(consensus.canReachConsensus()).toBe(false);
      });

      it("should validate minimum network size requirements", () => {
        const smallConsensus = new ByzantineConsensus("test", 2);

        // Register agents
        smallConsensus.registerAgent({
          id: "agent-1",
          publicKey: "key1",
          isLeader: false,
          reputation: 1.0,
          lastActiveTime: new Date(),
        });
        smallConsensus.registerAgent({
          id: "agent-2",
          publicKey: "key2",
          isLeader: false,
          reputation: 1.0,
          lastActiveTime: new Date(),
        });

        // Cannot reach Byzantine consensus with only 2 nodes
        expect(smallConsensus.canReachConsensus()).toBe(false);
      });

      it("should handle edge cases in fault calculations", () => {
        // Test boundary conditions
        const testCases = [
          { nodes: 3, malicious: 0, canConsensus: false }, // 3 < 3*0 + 1, but still too small
          { nodes: 4, malicious: 1, canConsensus: true }, // 4 >= 3*1 + 1
          { nodes: 6, malicious: 1, canConsensus: true }, // 6 >= 3*1 + 1
          { nodes: 7, malicious: 2, canConsensus: true }, // 7 >= 3*2 + 1
          { nodes: 7, malicious: 3, canConsensus: false }, // 7 < 3*3 + 1
        ];

        testCases.forEach(({ nodes, malicious, canConsensus }, index) => {
          const testConsensus = new ByzantineConsensus(`test-${index}`, nodes);

          // Register honest agents
          for (let i = 0; i < nodes - malicious; i++) {
            testConsensus.registerAgent({
              id: `honest-${i}`,
              publicKey: `key-${i}`,
              isLeader: false,
              reputation: 1.0,
              lastActiveTime: new Date(),
            });
          }

          // Register malicious agents
          for (let i = 0; i < malicious; i++) {
            testConsensus.registerAgent({
              id: `malicious-${i}`,
              publicKey: `bad-key-${i}`,
              isLeader: false,
              isMalicious: true,
              reputation: 0.1,
              lastActiveTime: new Date(),
            });
          }

          expect(testConsensus.canReachConsensus()).toBe(canConsensus);
        });
      });
    });

    describe("Quorum Formation and Validation", () => {
      it("should validate quorum formation for prepare phase", async () => {
        const proposal: ConsensusProposal = {
          id: "quorum-test",
          content: { action: "test" },
          proposerId: "agent-1",
          timestamp: new Date(),
          hash: "quorum-hash",
        };

        // Simulate prepare phase quorum collection
        const requiredPrepareResponses = 2; // 2f for 4 nodes
        let prepareResponses = 0;

        // Mock the internal prepare collection mechanism
        const collectPrepareResponses =
          consensus["collectPrepareResponses"].bind(consensus);

        // Simulate receiving prepare messages
        const prepareMessage: ConsensusMessage = {
          type: "prepare",
          viewNumber: 0,
          sequenceNumber: 1,
          digest: proposal.hash,
          payload: null,
          timestamp: new Date(),
          signature: "test-sig",
          senderId: "agent-2",
        };

        // Add multiple prepare messages to reach quorum
        const state = consensus.getState();
        state.proposals.set(proposal.id, proposal);
        state.messages.set(proposal.hash, [prepareMessage, prepareMessage]);

        // Test quorum validation
        expect(state.messages.get(proposal.hash)).toHaveLength(2);
      });

      it("should validate quorum formation for commit phase", async () => {
        const proposal: ConsensusProposal = {
          id: "commit-test",
          content: { action: "commit" },
          proposerId: "agent-1",
          timestamp: new Date(),
          hash: "commit-hash",
        };

        const requiredCommitResponses = 2; // 2f for 4 nodes

        // Simulate commit phase messages
        const commitMessages = [
          {
            type: "commit" as const,
            viewNumber: 0,
            sequenceNumber: 1,
            digest: proposal.hash,
            payload: null,
            timestamp: new Date(),
            signature: "sig1",
            senderId: "agent-2",
          },
          {
            type: "commit" as const,
            viewNumber: 0,
            sequenceNumber: 1,
            digest: proposal.hash,
            payload: null,
            timestamp: new Date(),
            signature: "sig2",
            senderId: "agent-3",
          },
        ];

        const state = consensus.getState();
        state.proposals.set(proposal.id, proposal);
        state.messages.set(proposal.hash, commitMessages);

        const commitCount = state.messages
          .get(proposal.hash)!
          .filter((m) => m.type === "commit" && m.viewNumber === 0).length;

        expect(commitCount).toBe(requiredCommitResponses);
      });

      it("should handle partial quorum scenarios", () => {
        const proposal: ConsensusProposal = {
          id: "partial-test",
          content: { action: "partial" },
          proposerId: "agent-1",
          timestamp: new Date(),
          hash: "partial-hash",
        };

        // Only 1 prepare message (need 2 for quorum)
        const partialMessages = [
          {
            type: "prepare" as const,
            viewNumber: 0,
            sequenceNumber: 1,
            digest: proposal.hash,
            payload: null,
            timestamp: new Date(),
            signature: "sig1",
            senderId: "agent-2",
          },
        ];

        const state = consensus.getState();
        state.proposals.set(proposal.id, proposal);
        state.messages.set(proposal.hash, partialMessages);

        const prepareCount = state.messages
          .get(proposal.hash)!
          .filter((m) => m.type === "prepare" && m.viewNumber === 0).length;

        expect(prepareCount).toBeLessThan(2); // Insufficient for quorum
      });
    });
  });

  describe("Consensus Protocol Flow", () => {
    describe("Three-Phase Commit Protocol", () => {
      it("should execute complete consensus flow successfully", async () => {
        const proposal: ConsensusProposal = {
          id: "flow-test",
          content: { operation: "update", value: 42 },
          proposerId: "agent-1",
          timestamp: new Date(),
          hash: "flow-hash",
        };

        // Track phases
        const phases: string[] = [];

        consensus.on("broadcast-message", (message: ConsensusMessage) => {
          phases.push(`${message.type}-sent`);
        });

        consensus.on(
          "consensus-reached",
          (acceptedProposal: ConsensusProposal) => {
            phases.push("consensus-complete");
            expect(acceptedProposal.id).toBe(proposal.id);
          },
        );

        // Mock leader status
        const state = consensus.getState();
        state.leader = "test-agent";

        // Start consensus
        const result = await consensus.startConsensus(proposal);

        // In a full implementation, we would simulate message exchanges
        // For now, verify the proposal was added to state
        expect(state.proposals.has(proposal.id)).toBe(true);
        expect(state.sequenceNumber).toBe(1);
      });

      it("should handle pre-prepare phase correctly", async () => {
        const proposal: ConsensusProposal = {
          id: "pre-prepare-test",
          content: { data: "pre-prepare" },
          proposerId: "test-agent",
          timestamp: new Date(),
          hash: "pre-prepare-hash",
        };

        let prePrepareMessage: ConsensusMessage | null = null;

        consensus.on("broadcast-message", (message: ConsensusMessage) => {
          if (message.type === "pre-prepare") {
            prePrepareMessage = message;
          }
        });

        const state = consensus.getState();
        state.leader = "test-agent";

        // Start consensus to trigger pre-prepare
        await consensus.startConsensus(proposal);

        // Verify pre-prepare was sent
        expect(prePrepareMessage).toBeDefined();
        expect(prePrepareMessage!.type).toBe("pre-prepare");
        expect(prePrepareMessage!.digest).toBe(proposal.hash);
        expect(state.phase).toBe("pre-prepare");
      });

      it("should validate prepare phase responses", async () => {
        const prepareMessage: ConsensusMessage = {
          type: "prepare",
          viewNumber: 0,
          sequenceNumber: 1,
          digest: "test-digest",
          payload: null,
          timestamp: new Date(),
          signature: "test-signature",
          senderId: "agent-2",
        };

        let prepareHandled = false;
        consensus.on("message-received", () => {
          prepareHandled = true;
        });

        await consensus.processMessage(prepareMessage);

        expect(prepareHandled).toBe(true);

        const state = consensus.getState();
        const messages = state.messages.get("test-digest") || [];
        expect(messages.some((m) => m.type === "prepare")).toBe(true);
      });

      it("should validate commit phase responses", async () => {
        const commitMessage: ConsensusMessage = {
          type: "commit",
          viewNumber: 0,
          sequenceNumber: 1,
          digest: "commit-digest",
          payload: null,
          timestamp: new Date(),
          signature: "commit-signature",
          senderId: "agent-3",
        };

        await consensus.processMessage(commitMessage);

        const state = consensus.getState();
        const messages = state.messages.get("commit-digest") || [];
        expect(messages.some((m) => m.type === "commit")).toBe(true);
      });
    });

    describe("Message Validation and Security", () => {
      it("should validate message authenticity", async () => {
        const validMessage: ConsensusMessage = {
          type: "prepare",
          viewNumber: 0,
          sequenceNumber: 1,
          digest: "valid-digest",
          payload: null,
          timestamp: new Date(),
          signature: "valid-signature",
          senderId: "agent-1",
        };

        const invalidMessage: ConsensusMessage = {
          type: "prepare",
          viewNumber: 0,
          sequenceNumber: 1,
          digest: "invalid-digest",
          payload: null,
          timestamp: new Date(),
          signature: "", // Invalid signature
          senderId: "unknown-agent", // Unknown sender
        };

        let invalidMessageCount = 0;
        consensus.on("invalid-message", () => {
          invalidMessageCount++;
        });

        await consensus.processMessage(validMessage);
        await consensus.processMessage(invalidMessage);

        expect(invalidMessageCount).toBe(1);
      });

      it("should detect malicious behavior", async () => {
        const maliciousMessage: ConsensusMessage = {
          type: "pre-prepare",
          viewNumber: 0,
          sequenceNumber: 1,
          digest: "malicious-digest",
          payload: null,
          timestamp: new Date(),
          signature: "malicious-signature",
          senderId: "agent-2", // Not the leader
        };

        let maliciousBehaviorDetected = false;
        consensus.on("malicious-behavior", (event) => {
          maliciousBehaviorDetected = true;
          expect(event.type).toBe("unauthorized-pre-prepare");
          expect(event.agentId).toBe("agent-2");
        });

        await consensus.processMessage(maliciousMessage);

        expect(maliciousBehaviorDetected).toBe(true);
      });

      it("should filter messages from malicious agents", async () => {
        const messageFromMalicious: ConsensusMessage = {
          type: "prepare",
          viewNumber: 0,
          sequenceNumber: 1,
          digest: "mal-digest",
          payload: null,
          timestamp: new Date(),
          signature: "mal-signature",
          senderId: "malicious-agent",
        };

        let processedCount = 0;
        consensus.on("message-received", () => {
          processedCount++;
        });

        await consensus.processMessage(messageFromMalicious);

        // Message should be rejected due to malicious sender
        expect(processedCount).toBe(0);
      });
    });
  });

  describe("View Change and Leader Election", () => {
    describe("View Change Mechanism", () => {
      it("should initiate view change on consensus failure", async () => {
        let viewChangeInitiated = false;
        consensus.on("view-change-initiated", (newView) => {
          viewChangeInitiated = true;
          expect(newView).toBe(1);
        });

        const initialState = consensus.getState();
        const initialView = initialState.currentView;

        // Trigger view change manually
        await (consensus as any).initiateViewChange();

        expect(viewChangeInitiated).toBe(true);

        const newState = consensus.getState();
        expect(newState.currentView).toBe(initialView + 1);
      });

      it("should elect new leader during view change", async () => {
        const initialState = consensus.getState();
        const initialLeader = initialState.leader;

        await (consensus as any).initiateViewChange();

        const newState = consensus.getState();
        const newLeader = newState.leader;

        // Leader should change in round-robin fashion
        expect(newLeader).not.toBe(initialLeader);
      });

      it("should handle view change messages correctly", async () => {
        const viewChangeMessage: ConsensusMessage = {
          type: "view-change",
          viewNumber: 1,
          sequenceNumber: 0,
          digest: "",
          payload: {
            lastCommitted: [],
            messageLog: [],
          },
          timestamp: new Date(),
          signature: "vc-signature",
          senderId: "agent-2",
        };

        // Need multiple view change messages for quorum
        const messages = Array.from({ length: 3 }, (_, i) => ({
          ...viewChangeMessage,
          senderId: `agent-${i + 1}`,
        }));

        // Process view change messages
        for (const message of messages) {
          await consensus.processMessage(message);
        }

        // Should trigger new view if this agent is the new leader
        const state = consensus.getState();
        expect(state.currentView).toBeGreaterThanOrEqual(1);
      });

      it("should validate new view messages", async () => {
        const newViewMessage: ConsensusMessage = {
          type: "new-view",
          viewNumber: 1,
          sequenceNumber: 0,
          digest: "",
          payload: {
            viewChangeMessages: [],
          },
          timestamp: new Date(),
          signature: "nv-signature",
          senderId: "agent-1",
        };

        let newViewAccepted = false;
        consensus.on("new-view-accepted", (viewNumber) => {
          newViewAccepted = true;
          expect(viewNumber).toBe(1);
        });

        // Mock that agent-1 is the new leader for view 1
        jest.spyOn(consensus as any, "selectLeader").mockReturnValue("agent-1");

        await consensus.processMessage(newViewMessage);

        expect(newViewAccepted).toBe(true);
      });
    });

    describe("Leader Election Algorithm", () => {
      it("should select leader deterministically", () => {
        const selectLeader = (consensus as any).selectLeader.bind(consensus);

        // Should always return same leader for same view
        const leader1 = selectLeader(0);
        const leader2 = selectLeader(0);
        expect(leader1).toBe(leader2);

        // Different views should potentially have different leaders
        const leader3 = selectLeader(1);
        expect(typeof leader3).toBe("string");
      });

      it("should rotate leadership fairly", () => {
        const selectLeader = (consensus as any).selectLeader.bind(consensus);
        const state = consensus.getState();
        const agentCount = state.activeAgents.size;

        const leaders = new Set();
        for (let view = 0; view < agentCount * 2; view++) {
          leaders.add(selectLeader(view));
        }

        // Should have used all agents as leaders over multiple rounds
        expect(leaders.size).toBe(agentCount);
      });

      it("should handle leadership validation", () => {
        const state = consensus.getState();
        const originalLeader = state.leader;

        // Test leader validation
        const isLeader1 = (consensus as any).isLeader();

        // Change leader
        state.leader = "test-agent";
        const isLeader2 = (consensus as any).isLeader();

        // Restore original
        state.leader = originalLeader;
        const isLeader3 = (consensus as any).isLeader();

        expect(isLeader2).toBe(true);
      });
    });
  });

  describe("Network Partitions and Recovery", () => {
    describe("Partition Simulation", () => {
      it("should handle network partition gracefully", () => {
        const initialAgentCount = consensus.getState().activeAgents.size;

        // Simulate partition by removing agents
        consensus.simulatePartition(["agent-1", "agent-2"]);

        const state = consensus.getState();
        expect(state.activeAgents.size).toBe(initialAgentCount - 2);
        expect(state.activeAgents.has("agent-1")).toBe(false);
        expect(state.activeAgents.has("agent-2")).toBe(false);
      });

      it("should detect when consensus becomes impossible due to partition", () => {
        // Start with 4 agents, can tolerate 1 fault
        expect(consensus.canReachConsensus()).toBe(true);

        // Partition 2 agents - now only 2 remain, cannot reach consensus
        consensus.simulatePartition(["agent-1", "agent-2"]);
        expect(consensus.canReachConsensus()).toBe(false);
      });

      it("should recover from partition when network heals", () => {
        // Simulate partition
        consensus.simulatePartition(["agent-1", "agent-2"]);
        expect(consensus.canReachConsensus()).toBe(false);

        // Heal partition
        consensus.healPartition(["agent-1", "agent-2"]);

        const state = consensus.getState();
        expect(state.activeAgents.has("agent-1")).toBe(true);
        expect(state.activeAgents.has("agent-2")).toBe(true);
        expect(consensus.canReachConsensus()).toBe(true);
      });

      it("should emit appropriate events for partition scenarios", () => {
        let partitionEvent: string[] = [];
        let healEvent: string[] = [];

        consensus.on("network-partition", (agentIds) => {
          partitionEvent = agentIds;
        });

        consensus.on("network-healed", (agentIds) => {
          healEvent = agentIds;
        });

        const partitionedAgents = ["agent-1", "agent-3"];

        consensus.simulatePartition(partitionedAgents);
        expect(partitionEvent).toEqual(partitionedAgents);

        consensus.healPartition(partitionedAgents);
        expect(healEvent).toEqual(partitionedAgents);
      });
    });

    describe("Partition Recovery Scenarios", () => {
      it("should handle majority partition scenarios", () => {
        // With 4 agents, partition into 3 vs 1
        consensus.simulatePartition(["agent-1"]); // Remove 1, keep 3

        // Majority partition (3 agents) should be able to continue
        expect(consensus.canReachConsensus()).toBe(true);
      });

      it("should handle minority partition scenarios", () => {
        // Partition into 2 vs 2 (both are minorities for Byzantine consensus)
        consensus.simulatePartition(["agent-1", "agent-2"]); // Remove 2, keep 2

        // Neither partition can reach consensus
        expect(consensus.canReachConsensus()).toBe(false);
      });

      it("should handle agents rejoining after partition", () => {
        const agent1Data = testAgents.find((a) => a.id === "agent-1")!;

        // Remove agent
        consensus.removeAgent("agent-1");
        expect(consensus.getState().activeAgents.has("agent-1")).toBe(false);

        // Re-register agent (simulating rejoin)
        consensus.registerAgent(agent1Data);
        expect(consensus.getState().activeAgents.has("agent-1")).toBe(true);
      });
    });
  });

  describe("Performance Metrics and Monitoring", () => {
    describe("Consensus Performance Tracking", () => {
      it("should track consensus round metrics", () => {
        const initialMetrics = consensus.getPerformanceMetrics();
        expect(initialMetrics.consensusRounds).toBe(0);
        expect(initialMetrics.averageLatency).toBe(0);
        expect(initialMetrics.successRate).toBe(0);
      });

      it("should update performance metrics after consensus", async () => {
        // Mock successful consensus
        const updatePerformance = (consensus as any).updatePerformance.bind(
          consensus,
        );

        const startTime = Date.now() - 1000; // 1 second ago
        updatePerformance(startTime, true);

        const metrics = consensus.getPerformanceMetrics();
        expect(metrics.consensusRounds).toBe(1);
        expect(metrics.averageLatency).toBeGreaterThan(0);
        expect(metrics.successRate).toBe(1.0);
      });

      it("should track fault detection metrics", async () => {
        const maliciousMessage: ConsensusMessage = {
          type: "prepare",
          viewNumber: 0,
          sequenceNumber: 1,
          digest: "test-digest",
          payload: null,
          timestamp: new Date(),
          signature: "invalid-signature",
          senderId: "malicious-agent",
        };

        await consensus.processMessage(maliciousMessage);

        const metrics = consensus.getPerformanceMetrics();
        expect(metrics.faultsDetected).toBeGreaterThan(0);
      });

      it("should calculate success rates accurately", async () => {
        const updatePerformance = (consensus as any).updatePerformance.bind(
          consensus,
        );

        // Simulate mixed results
        updatePerformance(Date.now(), true); // Success
        updatePerformance(Date.now(), false); // Failure
        updatePerformance(Date.now(), true); // Success

        const metrics = consensus.getPerformanceMetrics();
        expect(metrics.consensusRounds).toBe(3);
        expect(metrics.successRate).toBeCloseTo(2 / 3);
      });
    });

    describe("Latency and Throughput Metrics", () => {
      it("should measure consensus latency correctly", async () => {
        const updatePerformance = (consensus as any).updatePerformance.bind(
          consensus,
        );

        // Simulate different latencies
        const baseTime = Date.now();
        updatePerformance(baseTime - 100, true); // 100ms
        updatePerformance(baseTime - 200, true); // 200ms

        const metrics = consensus.getPerformanceMetrics();
        expect(metrics.averageLatency).toBe(150); // (100 + 200) / 2
      });

      it("should track consensus throughput over time", () => {
        const updatePerformance = (consensus as any).updatePerformance.bind(
          consensus,
        );

        // Simulate multiple consensus rounds
        for (let i = 0; i < 10; i++) {
          updatePerformance(Date.now() - 100, true);
        }

        const metrics = consensus.getPerformanceMetrics();
        expect(metrics.consensusRounds).toBe(10);
        expect(metrics.successRate).toBe(1.0);
      });
    });
  });

  describe("Edge Cases and Error Handling", () => {
    describe("Malicious Behavior Detection", () => {
      it("should detect double-spend attempts", async () => {
        // Simulate conflicting proposals from same agent
        const proposal1: ConsensusProposal = {
          id: "conflict-1",
          content: { account: "A", balance: -100 },
          proposerId: "agent-1",
          timestamp: new Date(),
          hash: "hash1",
        };

        const proposal2: ConsensusProposal = {
          id: "conflict-2",
          content: { account: "A", balance: -150 },
          proposerId: "agent-1",
          timestamp: new Date(),
          hash: "hash2",
        };

        // Both proposals from same agent in same view should be suspicious
        const state = consensus.getState();
        state.leader = "agent-1";

        // In real implementation, this would detect conflicting proposals
        expect(proposal1.proposerId).toBe(proposal2.proposerId);
      });

      it("should handle message replay attacks", async () => {
        const message: ConsensusMessage = {
          type: "prepare",
          viewNumber: 0,
          sequenceNumber: 1,
          digest: "replay-digest",
          payload: null,
          timestamp: new Date(Date.now() - 60000), // 1 minute old
          signature: "replay-signature",
          senderId: "agent-2",
        };

        // Process same message twice
        await consensus.processMessage(message);
        await consensus.processMessage(message);

        // Should only be processed once (duplicate detection)
        const state = consensus.getState();
        const messages = state.messages.get("replay-digest") || [];
        // In a full implementation, would check for duplicate filtering
        expect(messages.length).toBeGreaterThanOrEqual(1);
      });

      it("should validate message timing", async () => {
        const oldMessage: ConsensusMessage = {
          type: "prepare",
          viewNumber: 0,
          sequenceNumber: 1,
          digest: "old-digest",
          payload: null,
          timestamp: new Date(Date.now() - 3600000), // 1 hour old
          signature: "old-signature",
          senderId: "agent-2",
        };

        const futureMessage: ConsensusMessage = {
          type: "prepare",
          viewNumber: 0,
          sequenceNumber: 1,
          digest: "future-digest",
          payload: null,
          timestamp: new Date(Date.now() + 3600000), // 1 hour in future
          signature: "future-signature",
          senderId: "agent-2",
        };

        // In a full implementation, would validate timestamp bounds
        await consensus.processMessage(oldMessage);
        await consensus.processMessage(futureMessage);

        // Should handle timing validation appropriately
        expect(true).toBe(true); // Placeholder
      });
    });

    describe("Resource Exhaustion Scenarios", () => {
      it("should handle large numbers of concurrent proposals", async () => {
        const proposals = Array.from({ length: 100 }, (_, i) => ({
          id: `proposal-${i}`,
          content: { data: `data-${i}` },
          proposerId: "test-agent",
          timestamp: new Date(),
          hash: `hash-${i}`,
        }));

        const state = consensus.getState();
        state.leader = "test-agent";

        // Add all proposals
        proposals.forEach((proposal) => {
          state.proposals.set(proposal.id, proposal);
        });

        expect(state.proposals.size).toBe(100);
      });

      it("should handle message flooding", async () => {
        const messages = Array.from({ length: 1000 }, (_, i) => ({
          type: "prepare" as const,
          viewNumber: 0,
          sequenceNumber: i,
          digest: `digest-${i}`,
          payload: null,
          timestamp: new Date(),
          signature: `sig-${i}`,
          senderId: "agent-2",
        }));

        let processedCount = 0;
        consensus.on("message-received", () => {
          processedCount++;
        });

        // Process all messages
        for (const message of messages.slice(0, 10)) {
          // Limit for test performance
          await consensus.processMessage(message);
        }

        // Should handle gracefully without crashing
        expect(processedCount).toBeLessThanOrEqual(10);
      });
    });

    describe("State Consistency Validation", () => {
      it("should maintain consistent state across operations", async () => {
        const initialState = consensus.getState();
        const initialView = initialState.currentView;
        const initialSequence = initialState.sequenceNumber;

        // Perform various operations
        consensus.registerAgent({
          id: "new-agent",
          publicKey: "new-key",
          isLeader: false,
          reputation: 1.0,
          lastActiveTime: new Date(),
        });

        await (consensus as any).initiateViewChange();

        // State should remain consistent
        const finalState = consensus.getState();
        expect(finalState.currentView).toBeGreaterThan(initialView);
        expect(finalState.sequenceNumber).toBeGreaterThanOrEqual(
          initialSequence,
        );
      });

      it("should validate state transitions", () => {
        const state = consensus.getState();
        const validPhases = ["pre-prepare", "prepare", "commit", "view-change"];

        expect(validPhases).toContain(state.phase);
        expect(state.currentView).toBeGreaterThanOrEqual(0);
        expect(state.sequenceNumber).toBeGreaterThanOrEqual(0);
      });

      it("should handle concurrent state modifications safely", async () => {
        // Simulate concurrent operations
        const operations = [
          () =>
            consensus.registerAgent({
              id: "concurrent-1",
              publicKey: "key1",
              isLeader: false,
              reputation: 1.0,
              lastActiveTime: new Date(),
            }),
          () =>
            consensus.registerAgent({
              id: "concurrent-2",
              publicKey: "key2",
              isLeader: false,
              reputation: 1.0,
              lastActiveTime: new Date(),
            }),
          () => consensus.removeAgent("agent-1"),
        ];

        // Execute concurrently
        await Promise.all(operations.map((op) => op()));

        // State should remain consistent
        const state = consensus.getState();
        expect(state.activeAgents.size).toBeGreaterThan(0);
      });
    });
  });
});
