import {
  enrichAgentCard,
  verifyAgentCard,
  calculateStreetCred,
  buildExtensionData,
  fetchOnChainData,
} from '../src/enrich';
import type { AgentCard, EnrichedAgentCard, OnChainAgentData } 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');
}

// ─── Test fixtures ─────────────────────────────────────────────────────────────

const DEPLOYER_ADDRESS = '0xfA15Ee28939B222B0448261A22156070f0A7813C';

const sampleCard: AgentCard = {
  name: 'My A2A Agent',
  url: 'https://myagent.example.com',
  provider: { organization: 'Test Corp', url: 'https://testcorp.com' },
  version: '1.0.0',
  description: 'A test A2A agent',
  capabilities: {
    streaming: true,
    pushNotifications: false,
  },
  skills: [
    {
      id: 'code-review',
      name: 'Code Review',
      description: 'Reviews code for quality',
      tags: ['dev', 'quality'],
    },
  ],
};

// ─── Unit tests for calculateStreetCred ───────────────────────────────────────

describe('calculateStreetCred', () => {
  const now = BigInt(Math.floor(Date.now() / 1000));

  it('returns 0 for unregistered agent', () => {
    const data: OnChainAgentData = {
      identity: { agentURI: '', active: false, agentType: 'unknown', registeredAt: 0n },
      bonds: { total: 0, active: 0 },
      reputation: { averageRating: 0n, totalFeedbacks: 0n, verifiedFeedbacks: 0n, lastUpdated: 0n },
      isRecognized: false,
    };
    expect(calculateStreetCred(data)).toBe(0);
  });

  it('returns score for active agent with no bonds or reputation', () => {
    const registeredAt = now - BigInt(40 * 24 * 3600); // 40 days ago
    const data: OnChainAgentData = {
      identity: { agentURI: 'ipfs://test', active: true, agentType: 'autonomous', registeredAt },
      bonds: { total: 0, active: 0 },
      reputation: { averageRating: 0n, totalFeedbacks: 0n, verifiedFeedbacks: 0n, lastUpdated: 0n },
      isRecognized: false,
    };
    const score = calculateStreetCred(data);
    // Active = 20pts + age bonus 5pts = 25pts
    expect(score).toBe(25);
  });

  it('adds bond score (5 pts per active bond, max 30)', () => {
    const registeredAt = now - BigInt(40 * 24 * 3600);
    const data: OnChainAgentData = {
      identity: { agentURI: 'ipfs://test', active: true, agentType: 'autonomous', registeredAt },
      bonds: { total: 3, active: 3 },
      reputation: { averageRating: 0n, totalFeedbacks: 0n, verifiedFeedbacks: 0n, lastUpdated: 0n },
      isRecognized: false,
    };
    const score = calculateStreetCred(data);
    // 25 (identity) + 15 (3 bonds) = 40
    expect(score).toBe(40);
  });

  it('caps bond score at 30 points (6 bonds max)', () => {
    const registeredAt = now - BigInt(40 * 24 * 3600);
    const data: OnChainAgentData = {
      identity: { agentURI: 'ipfs://test', active: true, agentType: 'autonomous', registeredAt },
      bonds: { total: 10, active: 10 }, // would give 50 pts but capped at 30
      reputation: { averageRating: 0n, totalFeedbacks: 0n, verifiedFeedbacks: 0n, lastUpdated: 0n },
      isRecognized: false,
    };
    const score = calculateStreetCred(data);
    // 25 + 30 (capped) = 55
    expect(score).toBe(55);
  });

  it('adds bridge recognition bonus', () => {
    const registeredAt = now - BigInt(40 * 24 * 3600);
    const data: OnChainAgentData = {
      identity: { agentURI: 'ipfs://test', active: true, agentType: 'autonomous', registeredAt },
      bonds: { total: 0, active: 0 },
      reputation: { averageRating: 0n, totalFeedbacks: 0n, verifiedFeedbacks: 0n, lastUpdated: 0n },
      isRecognized: true,
    };
    const score = calculateStreetCred(data);
    // 25 + 10 = 35
    expect(score).toBe(35);
  });

  it('never exceeds 95', () => {
    const registeredAt = now - BigInt(40 * 24 * 3600);
    const data: OnChainAgentData = {
      identity: { agentURI: 'ipfs://test', active: true, agentType: 'autonomous', registeredAt },
      bonds: { total: 6, active: 6 },
      reputation: { averageRating: 500n, totalFeedbacks: 100n, verifiedFeedbacks: 100n, lastUpdated: now },
      isRecognized: true,
    };
    const score = calculateStreetCred(data);
    expect(score).toBeLessThanOrEqual(95);
  });
});

// ─── Unit tests for buildExtensionData ────────────────────────────────────────

describe('buildExtensionData', () => {
  const now = BigInt(Math.floor(Date.now() / 1000));

  it('builds correct extension data structure', () => {
    const onChain: OnChainAgentData = {
      identity: {
        agentURI: 'ipfs://QmTest',
        active: true,
        agentType: 'autonomous',
        registeredAt: now - BigInt(60 * 24 * 3600),
      },
      bonds: { total: 2, active: 2 },
      reputation: { averageRating: 420n, totalFeedbacks: 3n, verifiedFeedbacks: 3n, lastUpdated: now },
      isRecognized: false,
    };

    const ext = buildExtensionData('base', DEPLOYER_ADDRESS, onChain);

    expect(ext.chain).toBe('base');
    expect(ext.address).toBe(DEPLOYER_ADDRESS);
    expect(ext.streetCred.maxPossible).toBe(95);
    expect(ext.streetCred.score).toBeGreaterThan(0);
    expect(ext.streetCred.tier).toBeDefined();
    expect(ext.bonds.total).toBe(2);
    expect(ext.bonds.active).toBe(2);
    expect(ext.identity.registered).toBe(true);
    expect(ext.identity.active).toBe(true);
    expect(ext.identity.agentType).toBe('autonomous');
    expect(ext.verifiedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
  });

  it('calculates averageRating correctly (stored as x100)', () => {
    const now2 = BigInt(Math.floor(Date.now() / 1000));
    const onChain: OnChainAgentData = {
      identity: { agentURI: '', active: false, agentType: 'unknown', registeredAt: 0n },
      bonds: { total: 0, active: 0 },
      reputation: { averageRating: 450n, totalFeedbacks: 10n, verifiedFeedbacks: 10n, lastUpdated: now2 },
      isRecognized: false,
    };

    const ext = buildExtensionData('base', DEPLOYER_ADDRESS, onChain);
    expect(ext.reputation.averageRating).toBe(4.5);
    expect(ext.reputation.totalFeedbacks).toBe(10);
  });

  it('handles zero reputation gracefully', () => {
    const onChain: OnChainAgentData = {
      identity: { agentURI: '', active: false, agentType: 'unknown', registeredAt: 0n },
      bonds: { total: 0, active: 0 },
      reputation: { averageRating: 0n, totalFeedbacks: 0n, verifiedFeedbacks: 0n, lastUpdated: 0n },
      isRecognized: false,
    };
    const ext = buildExtensionData('avalanche', '0x1234567890123456789012345678901234567890', onChain);
    expect(ext.reputation.averageRating).toBe(0);
    expect(ext.reputation.totalFeedbacks).toBe(0);
  });
});

// ─── Integration tests: enrichAgentCard ───────────────────────────────────────

describe('enrichAgentCard (live RPC)', () => {
  it(
    'enriches an Agent Card for deployer on Base',
    async () => {
      const enriched = await withRetry(() =>
        enrichAgentCard({
          agentCard: sampleCard,
          chain: 'base',
          agentAddress: DEPLOYER_ADDRESS,
        })
      );

      expect(enriched.name).toBe(sampleCard.name);
      expect(enriched.url).toBe(sampleCard.url);
      expect(enriched['x-vaultfire']).toBeDefined();
      expect(enriched['x-vaultfire'].chain).toBe('base');
      expect(enriched['x-vaultfire'].address).toBe(DEPLOYER_ADDRESS);
      expect(enriched['x-vaultfire'].streetCred.maxPossible).toBe(95);
      expect(enriched['x-vaultfire'].streetCred.score).toBeGreaterThanOrEqual(0);
      expect(enriched['x-vaultfire'].streetCred.score).toBeLessThanOrEqual(95);
      expect(enriched['x-vaultfire'].verifiedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
    },
    60000
  );

  it(
    'enriches an Agent Card for deployer on Avalanche',
    async () => {
      const enriched = await withRetry(() =>
        enrichAgentCard({
          agentCard: sampleCard,
          chain: 'avalanche',
          agentAddress: DEPLOYER_ADDRESS,
        })
      );

      expect(enriched['x-vaultfire']).toBeDefined();
      expect(enriched['x-vaultfire'].chain).toBe('avalanche');
    },
    60000
  );

  it(
    'enriches an Agent Card for deployer on Arbitrum',
    async () => {
      const enriched = await withRetry(() =>
        enrichAgentCard({
          agentCard: sampleCard,
          chain: 'arbitrum',
          agentAddress: DEPLOYER_ADDRESS,
        })
      );

      expect(enriched['x-vaultfire']).toBeDefined();
      expect(enriched['x-vaultfire'].chain).toBe('arbitrum');
    },
    60000
  );

  it(
    'enriches an Agent Card for deployer on Polygon',
    async () => {
      const enriched = await withRetry(() =>
        enrichAgentCard({
          agentCard: sampleCard,
          chain: 'polygon',
          agentAddress: DEPLOYER_ADDRESS,
        })
      );

      expect(enriched['x-vaultfire']).toBeDefined();
      expect(enriched['x-vaultfire'].chain).toBe('polygon');
    },
    60000
  );

  it('throws on invalid address', async () => {
    await expect(
      enrichAgentCard({
        agentCard: sampleCard,
        chain: 'base',
        agentAddress: 'not-an-address',
      })
    ).rejects.toThrow('Invalid agent address');
  });

  it('throws on unsupported chain', async () => {
    await expect(
      enrichAgentCard({
        agentCard: sampleCard,
        chain: 'unsupported-chain' as any,
        agentAddress: DEPLOYER_ADDRESS,
      })
    ).rejects.toThrow('Unsupported chain');
  });

  it(
    'preserves all original Agent Card fields',
    async () => {
      const enriched = await withRetry(() =>
        enrichAgentCard({
          agentCard: sampleCard,
          chain: 'base',
          agentAddress: DEPLOYER_ADDRESS,
        })
      );

      expect(enriched.name).toBe(sampleCard.name);
      expect(enriched.url).toBe(sampleCard.url);
      expect(enriched.description).toBe(sampleCard.description);
      expect(enriched.version).toBe(sampleCard.version);
      expect(enriched.skills).toEqual(sampleCard.skills);
      expect(enriched.capabilities).toEqual(sampleCard.capabilities);
    },
    60000
  );
});

// ─── Integration tests: verifyAgentCard ──────────────────────────────────────

describe('verifyAgentCard (live RPC)', () => {
  let enrichedCard: EnrichedAgentCard;

  beforeAll(async () => {
    enrichedCard = await withRetry(() =>
      enrichAgentCard({
        agentCard: sampleCard,
        chain: 'base',
        agentAddress: DEPLOYER_ADDRESS,
      })
    );
  }, 60000);

  it(
    'returns a VerificationResult for a freshly enriched card',
    async () => {
      const result = await withRetry(() => verifyAgentCard(enrichedCard));

      expect(result.chain).toBe('base');
      expect(result.address).toBe(DEPLOYER_ADDRESS);
      expect(typeof result.trusted).toBe('boolean');
      expect(result.reason).toBeTruthy();
      expect(result.streetCred.maxPossible).toBe(95);
      expect(result.verifiedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
    },
    60000
  );

  it('returns untrusted for a card with no x-vaultfire extension', async () => {
    const result = await verifyAgentCard(sampleCard as unknown as EnrichedAgentCard);
    expect(result.trusted).toBe(false);
    expect(result.reason).toContain('No x-vaultfire');
  });

  it('returns untrusted for a card with invalid schema', async () => {
    const badCard = {
      ...sampleCard,
      'x-vaultfire': { chain: 'base', address: 'invalid', score: -1 },
    } as unknown as EnrichedAgentCard;

    const result = await verifyAgentCard(badCard);
    expect(result.trusted).toBe(false);
  });
});
