import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import nock from 'nock'
import { WebhookClient } from '../webhook-client'
import type { HijackEvent } from '../types'

describe('WebhookClient', () => {
  let client: WebhookClient
  const testEvent: HijackEvent = {
    id: 1,
    asn: 13335,
    prefix: '185.1.0.0/24',
    detected_at: new Date('2026-04-29T10:00:00Z'),
    expected_asn: 13335,
    detected_asns: [12345],
    hijack_type: 'HIJACK',
    severity: 'HIGH',
    description: 'Potential BGP hijack detected',
    details: { source: 'bgp_monitor', confidence: 0.95 },
    resolved: false,
    resolved_at: null,
    created_at: new Date('2026-04-29T10:00:00Z'),
  }

  beforeEach(() => {
    client = new WebhookClient()
    nock.cleanAll()
  })

  afterEach(() => {
    nock.cleanAll()
  })

  describe('sendWebhook', () => {
    it('should send webhook with correct headers', async () => {
      const endpoint = 'https://webhook.example.com/notify'
      const secret = 'test_secret_key'

      nock('https://webhook.example.com')
        .post('/notify', (body) => {
          return typeof body === 'object' && 'event' in body && 'timestamp' in body
        })
        .reply(200, { success: true })

      const result = await client.sendWebhook(testEvent, endpoint, secret, 5000)

      expect(result.success).toBe(true)
      expect(result.status).toBe(200)
      expect(result.response_time_ms).toBeGreaterThanOrEqual(0)
    })

    it('should include HMAC signature in headers', async () => {
      const endpoint = 'https://webhook.example.com/notify'
      const secret = 'test_secret_key'
      let capturedHeaders: Record<string, string | string[]> | undefined

      nock('https://webhook.example.com')
        .post('/notify')
        .reply(200, function () {
          capturedHeaders = this.req.getHeaders()
          return { success: true }
        })

      await client.sendWebhook(testEvent, endpoint, secret, 5000)

      expect(capturedHeaders).toBeDefined()
      expect(capturedHeaders?.['x-webhook-signature']).toBeDefined()
      expect(typeof capturedHeaders?.['x-webhook-signature']).toBe('string')
    })

    it('should handle timeout errors', async () => {
      const endpoint = 'https://webhook.example.com/notify'
      const secret = 'test_secret_key'

      nock('https://webhook.example.com')
        .post('/notify')
        .delayConnection(10000)
        .reply(200)

      const result = await client.sendWebhook(testEvent, endpoint, secret, 100)

      expect(result.success).toBe(false)
      expect(result.error).toBeDefined()
    })

    it('should return error on 5xx responses', async () => {
      const endpoint = 'https://webhook.example.com/notify'
      const secret = 'test_secret_key'

      nock('https://webhook.example.com')
        .post('/notify')
        .reply(500, { error: 'Internal Server Error' })

      const result = await client.sendWebhook(testEvent, endpoint, secret, 5000)

      expect(result.success).toBe(false)
      expect(result.status).toBe(500)
      expect(result.error).toBeDefined()
    })

    it('should succeed on 2xx responses', async () => {
      const endpoint = 'https://webhook.example.com/notify'
      const secret = 'test_secret_key'

      nock('https://webhook.example.com')
        .post('/notify')
        .reply(201, { id: 'evt_123' })

      const result = await client.sendWebhook(testEvent, endpoint, secret, 5000)

      expect(result.success).toBe(true)
      expect(result.status).toBe(201)
    })

    it('should return error on network failure', async () => {
      const endpoint = 'https://webhook.example.com/notify'
      const secret = 'test_secret_key'

      nock('https://webhook.example.com')
        .post('/notify')
        .replyWithError('ECONNREFUSED')

      const result = await client.sendWebhook(testEvent, endpoint, secret, 5000)

      expect(result.success).toBe(false)
      expect(result.error).toBeDefined()
    })
  })

  describe('calculateBackoffDelay', () => {
    it('should calculate exponential backoff', () => {
      expect(client.calculateBackoffDelay(1)).toBe(1000)
      expect(client.calculateBackoffDelay(2)).toBe(2000)
      expect(client.calculateBackoffDelay(3)).toBe(4000)
      expect(client.calculateBackoffDelay(4)).toBe(8000)
      expect(client.calculateBackoffDelay(5)).toBe(16000)
    })

    it('should cap delay at maximum', () => {
      expect(client.calculateBackoffDelay(6)).toBe(32000)
      expect(client.calculateBackoffDelay(10)).toBe(32000)
    })

    it('should handle attempt number 0 correctly', () => {
      expect(client.calculateBackoffDelay(0)).toBe(0.5)
    })
  })
})
