import {
  discoverTrustedAgents,
  getTotalAgentCount,
  isAgentRecognized,
} from '../src/discover';
import { CHAIN_CONFIGS, SUPPORTED_CHAINS, type SupportedChain } from '../src/types';

// ─── withRetry helper ──────────────────────────────────────────────────────────

async function withRetry<T>(
  fn: () => Promise<T>,
  retries = 3,
  delayMs = 2000
): Promise<T> {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === retries) throw err;
      await new Promise((resolve) => setTimeout(resolve, delayMs));
    }
  }
  throw new Error('unreachable');
}

const DEPLOYER_ADDRESS = '0xfA15Ee28939B222B0448261A22156070f0A7813C';

// ─── getTotalAgentCount ───────────────────────────────────────────────────────

describe('getTotalAgentCount (live RPC)', () => {
  it(
    'returns a non-negative number for Base',
    async () => {
      const count = await withRetry(() => getTotalAgentCount('base'));
      expect(typeof count).toBe('number');
      expect(count).toBeGreaterThanOrEqual(0);
    },
    60000
  );

  it(
    'returns a non-negative number for Avalanche',
    async () => {
      const count = await withRetry(() => getTotalAgentCount('avalanche'));
      expect(count).toBeGreaterThanOrEqual(0);
    },
    60000
  );

  it(
    'returns a non-negative number for Arbitrum',
    async () => {
      const count = await withRetry(() => getTotalAgentCount('arbitrum'));
      expect(count).toBeGreaterThanOrEqual(0);
    },
    60000
  );

  it(
    'returns a non-negative number for Polygon',
    async () => {
      const count = await withRetry(() => getTotalAgentCount('polygon'));
      expect(count).toBeGreaterThanOrEqual(0);
    },
    60000
  );
});

// ─── isAgentRecognized ────────────────────────────────────────────────────────

describe('isAgentRecognized (live RPC)', () => {
  it(
    'returns boolean for deployer address on Base',
    async () => {
      const recognized = await withRetry(() => isAgentRecognized('base', DEPLOYER_ADDRESS));
      expect(typeof recognized).toBe('boolean');
    },
    60000
  );

  it(
    'returns boolean for deployer address on Avalanche',
    async () => {
      const recognized = await withRetry(() => isAgentRecognized('avalanche', DEPLOYER_ADDRESS));
      expect(typeof recognized).toBe('boolean');
    },
    60000
  );

  it(
    'returns boolean for deployer address on Arbitrum',
    async () => {
      const recognized = await withRetry(() => isAgentRecognized('arbitrum', DEPLOYER_ADDRESS));
      expect(typeof recognized).toBe('boolean');
    },
    60000
  );

  it(
    'returns boolean for deployer address on Polygon',
    async () => {
      const recognized = await withRetry(() => isAgentRecognized('polygon', DEPLOYER_ADDRESS));
      expect(typeof recognized).toBe('boolean');
    },
    60000
  );
});

// ─── discoverTrustedAgents ────────────────────────────────────────────────────

describe('discoverTrustedAgents (live RPC)', () => {
  it(
    'returns an array (possibly empty) on Base',
    async () => {
      const agents = await withRetry(() =>
        discoverTrustedAgents({ chain: 'base', minStreetCred: 0, requireActive: false })
      );
      expect(Array.isArray(agents)).toBe(true);
    },
    90000
  );

  it(
    'returns agents sorted by Street Cred score descending',
    async () => {
      const agents = await withRetry(() =>
        discoverTrustedAgents({ chain: 'base', minStreetCred: 0, requireActive: false, limit: 10 })
      );
      for (let i = 1; i < agents.length; i++) {
        expect(agents[i].streetCred.score).toBeLessThanOrEqual(agents[i - 1].streetCred.score);
      }
    },
    90000
  );

  it(
    'filters by minStreetCred',
    async () => {
      const agents = await withRetry(() =>
        discoverTrustedAgents({ chain: 'base', minStreetCred: 50, requireActive: false })
      );
      for (const agent of agents) {
        expect(agent.streetCred.score).toBeGreaterThanOrEqual(50);
      }
    },
    90000
  );

  it(
    'respects the limit parameter',
    async () => {
      const agents = await withRetry(() =>
        discoverTrustedAgents({ chain: 'base', minStreetCred: 0, requireActive: false, limit: 5 })
      );
      expect(agents.length).toBeLessThanOrEqual(5);
    },
    90000
  );

  it(
    'each discovered agent has required fields',
    async () => {
      const agents = await withRetry(() =>
        discoverTrustedAgents({ chain: 'base', minStreetCred: 0, requireActive: false, limit: 3 })
      );
      for (const agent of agents) {
        expect(agent.address).toMatch(/^0x[0-9a-fA-F]{40}$/);
        expect(agent.chain).toBe('base');
        expect(typeof agent.agentURI).toBe('string');
        expect(typeof agent.agentType).toBe('string');
        expect(agent.streetCred.maxPossible).toBe(95);
        expect(agent.streetCred.score).toBeGreaterThanOrEqual(0);
        expect(agent.streetCred.score).toBeLessThanOrEqual(95);
        expect(typeof agent.bonds.total).toBe('number');
        expect(typeof agent.bonds.active).toBe('number');
        expect(typeof agent.active).toBe('boolean');
      }
    },
    90000
  );

  it('throws on unsupported chain', async () => {
    await expect(
      discoverTrustedAgents({ chain: 'unsupported-chain' as SupportedChain })
    ).rejects.toThrow('Unsupported chain');
  });
});

// ─── Contract address validation ─────────────────────────────────────────────

describe('Contract Address Validation', () => {
  it('all contract addresses are valid checksummed Ethereum addresses', () => {
    for (const chain of SUPPORTED_CHAINS) {
      const contracts = CHAIN_CONFIGS[chain].contracts;
      const addressPattern = /^0x[0-9a-fA-F]{40}$/;
      expect(contracts.identity).toMatch(addressPattern);
      expect(contracts.partnership).toMatch(addressPattern);
      expect(contracts.reputation).toMatch(addressPattern);
      expect(contracts.bridge).toMatch(addressPattern);
    }
  });
});
