import { describe, it, expect, vi, beforeEach } from 'vitest'
import { HijackAlertsDatabaseClient } from '../db-client'
import type { HijackEvent, WebhookSubscription, WebhookDelivery, CreateHijackEventInput, CreateWebhookInput } from '../types'

describe('HijackAlertsDatabaseClient', () => {
  let client: HijackAlertsDatabaseClient
  let mockPool: any

  beforeEach(() => {
    mockPool = {
      query: vi.fn(),
    }
    client = new HijackAlertsDatabaseClient(mockPool)
  })

  describe('insertHijackEvent', () => {
    it('should insert a hijack event and return the mapped result', async () => {
      const input: CreateHijackEventInput = {
        asn: 13335,
        prefix: '1.1.1.0/24',
        expected_asn: 13335,
        detected_asns: [13336, 13337],
        hijack_type: 'MOAS',
        severity: 'CRITICAL',
        description: 'Multiple origin ASes detected',
        details: { evidence: 'BGP announcement' },
      }

      const mockRow = {
        id: 1,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: '2024-04-15T10:00:00Z',
        expected_asn: 13335,
        detected_asns: [13336, 13337],
        hijack_type: 'MOAS',
        severity: 'CRITICAL',
        description: 'Multiple origin ASes detected',
        details: { evidence: 'BGP announcement' },
        resolved: false,
        resolved_at: null,
        created_at: '2024-04-15T10:00:00Z',
      }

      mockPool.query.mockResolvedValue({ rows: [mockRow] })

      const result = await client.insertHijackEvent(input)

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('INSERT INTO hijack_events'),
        expect.arrayContaining([
          13335,
          '1.1.1.0/24',
          13335,
          [13336, 13337],
          'MOAS',
          'CRITICAL',
          'Multiple origin ASes detected',
        ])
      )
      expect(result).toEqual({
        id: 1,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: expect.any(Date),
        expected_asn: 13335,
        detected_asns: [13336, 13337],
        hijack_type: 'MOAS',
        severity: 'CRITICAL',
        description: 'Multiple origin ASes detected',
        details: { evidence: 'BGP announcement' },
        resolved: false,
        resolved_at: null,
        created_at: expect.any(Date),
      })
    })

    it('should handle different hijack types', async () => {
      const testCases: Array<CreateHijackEventInput['hijack_type']> = ['MOAS', 'HIJACK', 'LEAK']

      for (const hijackType of testCases) {
        mockPool.query.mockResolvedValue({
          rows: [
            {
              id: 1,
              asn: 15169,
              prefix: '8.8.8.0/24',
              detected_at: '2024-04-15T10:00:00Z',
              expected_asn: 15169,
              detected_asns: [15170],
              hijack_type: hijackType,
              severity: 'HIGH',
              description: `${hijackType} detected`,
              details: {},
              resolved: false,
              resolved_at: null,
              created_at: '2024-04-15T10:00:00Z',
            },
          ],
        })

        const input: CreateHijackEventInput = {
          asn: 15169,
          prefix: '8.8.8.0/24',
          expected_asn: 15169,
          detected_asns: [15170],
          hijack_type: hijackType,
          severity: 'HIGH',
          description: `${hijackType} detected`,
          details: {},
        }

        const result = await client.insertHijackEvent(input)
        expect(result.hijack_type).toBe(hijackType)
      }
    })
  })

  describe('getRecentHijackEvent', () => {
    it('should return a recent hijack event if found', async () => {
      const mockRow = {
        id: 5,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: '2024-04-15T10:00:00Z',
        expected_asn: 13335,
        detected_asns: [13336],
        hijack_type: 'MOAS',
        severity: 'HIGH',
        description: 'MOAS detected',
        details: {},
        resolved: false,
        resolved_at: null,
        created_at: '2024-04-15T10:00:00Z',
      }

      mockPool.query.mockResolvedValue({ rows: [mockRow] })

      const result = await client.getRecentHijackEvent(13335, '1.1.1.0/24', 6)

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('SELECT * FROM hijack_events'),
        expect.arrayContaining([13335, '1.1.1.0/24'])
      )
      expect(result).not.toBeNull()
      expect(result?.id).toBe(5)
      expect(result?.asn).toBe(13335)
    })

    it('should return null if no recent hijack event found', async () => {
      mockPool.query.mockResolvedValue({ rows: [] })

      const result = await client.getRecentHijackEvent(13335, '1.1.1.0/24', 6)

      expect(result).toBeNull()
    })

    it('should use default 6 hours lookback', async () => {
      mockPool.query.mockResolvedValue({ rows: [] })

      await client.getRecentHijackEvent(13335, '1.1.1.0/24')

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining("INTERVAL '6 hours'"),
        expect.any(Array)
      )
    })
  })

  describe('getHijacksByAsn', () => {
    it('should return hijacks with pagination', async () => {
      const mockRows = [
        {
          id: 1,
          asn: 13335,
          prefix: '1.1.1.0/24',
          detected_at: '2024-04-15T10:00:00Z',
          expected_asn: 13335,
          detected_asns: [13336],
          hijack_type: 'MOAS',
          severity: 'HIGH',
          description: 'Event 1',
          details: {},
          resolved: false,
          resolved_at: null,
          created_at: '2024-04-15T10:00:00Z',
        },
      ]

      mockPool.query
        .mockResolvedValueOnce({ rows: mockRows })
        .mockResolvedValueOnce({ rows: [{ total: '10' }] })

      const result = await client.getHijacksByAsn(13335, 50, 0)

      expect(result.events).toHaveLength(1)
      expect(result.total).toBe(10)
      expect(result.events[0].asn).toBe(13335)
    })

    it('should filter by resolved status', async () => {
      mockPool.query
        .mockResolvedValueOnce({ rows: [] })
        .mockResolvedValueOnce({ rows: [{ total: '0' }] })

      await client.getHijacksByAsn(13335, 50, 0, true)

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('AND resolved = $2'),
        expect.arrayContaining([13335, true])
      )
    })

    it('should return empty results', async () => {
      mockPool.query
        .mockResolvedValueOnce({ rows: [] })
        .mockResolvedValueOnce({ rows: [{ total: '0' }] })

      const result = await client.getHijacksByAsn(99999, 50, 0)

      expect(result.events).toHaveLength(0)
      expect(result.total).toBe(0)
    })
  })

  describe('resolveHijack', () => {
    it('should mark hijack as resolved', async () => {
      const mockRow = {
        id: 1,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: '2024-04-15T10:00:00Z',
        expected_asn: 13335,
        detected_asns: [13336],
        hijack_type: 'MOAS',
        severity: 'HIGH',
        description: 'Event',
        details: {},
        resolved: true,
        resolved_at: '2024-04-15T11:00:00Z',
        created_at: '2024-04-15T10:00:00Z',
      }

      mockPool.query.mockResolvedValue({ rows: [mockRow] })

      const result = await client.resolveHijack(1, 'False positive')

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('UPDATE hijack_events SET resolved = true'),
        [1]
      )
      expect(result.resolved).toBe(true)
      expect(result.resolved_at).not.toBeNull()
    })
  })

  describe('createWebhookSubscription', () => {
    it('should create a webhook subscription', async () => {
      const input: CreateWebhookInput = {
        asn: 13335,
        endpoint_url: 'https://example.com/webhook',
        timeout_ms: 5000,
        max_retries: 5,
      }

      const mockRow = {
        id: 1,
        asn: 13335,
        endpoint_url: 'https://example.com/webhook',
        secret_key: 'sk_test_123',
        created_at: '2024-04-15T10:00:00Z',
        last_triggered_at: null,
        failure_count: 0,
        active: true,
        max_retries: 5,
        timeout_ms: 5000,
        metadata: {},
      }

      mockPool.query.mockResolvedValue({ rows: [mockRow] })

      const result = await client.createWebhookSubscription(input, 'sk_test_123')

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('INSERT INTO webhook_subscriptions'),
        expect.arrayContaining([13335, 'https://example.com/webhook', 'sk_test_123', 5000, 5])
      )
      expect(result.id).toBe(1)
      expect(result.asn).toBe(13335)
      expect(result.active).toBe(true)
    })

    it('should use default values for optional parameters', async () => {
      const input: CreateWebhookInput = {
        asn: 13335,
        endpoint_url: 'https://example.com/webhook',
      }

      const mockRow = {
        id: 1,
        asn: 13335,
        endpoint_url: 'https://example.com/webhook',
        secret_key: 'sk_test_123',
        created_at: '2024-04-15T10:00:00Z',
        last_triggered_at: null,
        failure_count: 0,
        active: true,
        max_retries: 3,
        timeout_ms: 10000,
        metadata: {},
      }

      mockPool.query.mockResolvedValue({ rows: [mockRow] })

      await client.createWebhookSubscription(input, 'sk_test_123')

      expect(mockPool.query).toHaveBeenCalledWith(expect.any(String), expect.arrayContaining([13335, 'https://example.com/webhook', 'sk_test_123', 10000, 3]))
    })
  })

  describe('getWebhooksByAsn', () => {
    it('should return webhooks for an ASN', async () => {
      const mockRows = [
        {
          id: 1,
          asn: 13335,
          endpoint_url: 'https://example.com/webhook',
          secret_key: 'sk_test_123',
          created_at: '2024-04-15T10:00:00Z',
          last_triggered_at: null,
          failure_count: 0,
          active: true,
          max_retries: 3,
          timeout_ms: 10000,
          metadata: {},
        },
      ]

      mockPool.query.mockResolvedValue({ rows: mockRows })

      const result = await client.getWebhooksByAsn(13335)

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('SELECT * FROM webhook_subscriptions'),
        [13335]
      )
      expect(result).toHaveLength(1)
      expect(result[0].asn).toBe(13335)
    })

    it('should return empty array if no webhooks found', async () => {
      mockPool.query.mockResolvedValue({ rows: [] })

      const result = await client.getWebhooksByAsn(99999)

      expect(result).toHaveLength(0)
    })
  })

  describe('getWebhookSubscription', () => {
    it('should return a webhook subscription by ID', async () => {
      const mockRow = {
        id: 1,
        asn: 13335,
        endpoint_url: 'https://example.com/webhook',
        secret_key: 'sk_test_123',
        created_at: '2024-04-15T10:00:00Z',
        last_triggered_at: null,
        failure_count: 0,
        active: true,
        max_retries: 3,
        timeout_ms: 10000,
        metadata: {},
      }

      mockPool.query.mockResolvedValue({ rows: [mockRow] })

      const result = await client.getWebhookSubscription(1)

      expect(result).not.toBeNull()
      expect(result?.id).toBe(1)
    })

    it('should return null if webhook not found', async () => {
      mockPool.query.mockResolvedValue({ rows: [] })

      const result = await client.getWebhookSubscription(99999)

      expect(result).toBeNull()
    })
  })

  describe('deleteWebhookSubscription', () => {
    it('should delete a webhook subscription', async () => {
      mockPool.query.mockResolvedValue({})

      await client.deleteWebhookSubscription(1)

      expect(mockPool.query).toHaveBeenCalledWith('DELETE FROM webhook_subscriptions WHERE id = $1', [1])
    })
  })

  describe('updateWebhookStatus', () => {
    it('should update webhook active status', async () => {
      const mockRow = {
        id: 1,
        asn: 13335,
        endpoint_url: 'https://example.com/webhook',
        secret_key: 'sk_test_123',
        created_at: '2024-04-15T10:00:00Z',
        last_triggered_at: null,
        failure_count: 0,
        active: false,
        max_retries: 3,
        timeout_ms: 10000,
        metadata: {},
      }

      mockPool.query.mockResolvedValue({ rows: [mockRow] })

      const result = await client.updateWebhookStatus(1, false)

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('UPDATE webhook_subscriptions SET active = $1'),
        [false, 1]
      )
      expect(result?.active).toBe(false)
    })

    it('should return null if webhook not found', async () => {
      mockPool.query.mockResolvedValue({ rows: [] })

      const result = await client.updateWebhookStatus(99999, true)

      expect(result).toBeNull()
    })
  })

  describe('recordWebhookDelivery', () => {
    it('should record successful webhook delivery', async () => {
      const mockRow = {
        id: 1,
        subscription_id: 1,
        event_id: 1,
        attempt_number: 1,
        sent_at: '2024-04-15T10:00:00Z',
        response_status: 200,
        response_body: 'OK',
        error_message: null,
        next_retry_at: null,
      }

      mockPool.query.mockResolvedValue({ rows: [mockRow] })

      const result = await client.recordWebhookDelivery(1, 1, 1, 200, 'OK', null)

      expect(result.response_status).toBe(200)
      expect(result.next_retry_at).toBeNull()
    })

    it('should record failed webhook delivery with retry time', async () => {
      const mockRow = {
        id: 2,
        subscription_id: 1,
        event_id: 1,
        attempt_number: 1,
        sent_at: '2024-04-15T10:00:00Z',
        response_status: 500,
        response_body: null,
        error_message: 'Internal Server Error',
        next_retry_at: '2024-04-15T10:02:00Z',
      }

      mockPool.query.mockResolvedValue({ rows: [mockRow] })

      const result = await client.recordWebhookDelivery(1, 1, 1, 500, null, 'Internal Server Error')

      expect(result.response_status).toBe(500)
      expect(result.next_retry_at).not.toBeNull()
    })
  })

  describe('getFailedDeliveriesForRetry', () => {
    it('should return failed deliveries ready for retry', async () => {
      const mockRows = [
        {
          ws_id: 1,
          ws_asn: 13335,
          endpoint_url: 'https://example.com/webhook',
          secret_key: 'sk_test_123',
          ws_created_at: '2024-04-15T10:00:00Z',
          last_triggered_at: null,
          failure_count: 0,
          active: true,
          max_retries: 3,
          timeout_ms: 10000,
          metadata: {},
          he_id: 1,
          he_asn: 13335,
          prefix: '1.1.1.0/24',
          detected_at: '2024-04-15T10:00:00Z',
          expected_asn: 13335,
          detected_asns: [13336],
          hijack_type: 'MOAS',
          severity: 'HIGH',
          description: 'Event',
          details: {},
          resolved: false,
          resolved_at: null,
          he_created_at: '2024-04-15T10:00:00Z',
          wd_id: 1,
          wd_subscription_id: 1,
          wd_event_id: 1,
          wd_attempt_number: 1,
          wd_sent_at: '2024-04-15T10:00:00Z',
          wd_response_status: 500,
          wd_response_body: null,
          wd_error_message: 'Error',
          wd_next_retry_at: '2024-04-15T10:02:00Z',
        },
      ]

      mockPool.query.mockResolvedValue({ rows: mockRows })

      const result = await client.getFailedDeliveriesForRetry()

      expect(result).toHaveLength(1)
      expect(result[0].delivery.response_status).toBe(500)
      expect(result[0].webhook.asn).toBe(13335)
      expect(result[0].event.asn).toBe(13335)
    })

    it('should return empty array if no failed deliveries', async () => {
      mockPool.query.mockResolvedValue({ rows: [] })

      const result = await client.getFailedDeliveriesForRetry()

      expect(result).toHaveLength(0)
    })
  })

  describe('updateWebhookLastTriggered', () => {
    it('should update webhook last triggered time and failure count', async () => {
      mockPool.query.mockResolvedValue({})

      await client.updateWebhookLastTriggered(1, 0)

      expect(mockPool.query).toHaveBeenCalledWith(
        expect.stringContaining('UPDATE webhook_subscriptions SET last_triggered_at = NOW()'),
        [0, 1]
      )
    })

    it('should increment failure count on retry', async () => {
      mockPool.query.mockResolvedValue({})

      await client.updateWebhookLastTriggered(1, 2)

      expect(mockPool.query).toHaveBeenCalledWith(expect.any(String), [2, 1])
    })
  })

  describe('row mapping', () => {
    it('should correctly map database row to HijackEvent', async () => {
      const mockRow = {
        id: 1,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: '2024-04-15T10:00:00Z',
        expected_asn: 13335,
        detected_asns: [13336],
        hijack_type: 'MOAS',
        severity: 'CRITICAL',
        description: 'Test event',
        details: { test: true },
        resolved: false,
        resolved_at: null,
        created_at: '2024-04-15T10:00:00Z',
      }

      mockPool.query.mockResolvedValue({ rows: [mockRow] })

      const result = await client.insertHijackEvent({
        asn: 13335,
        prefix: '1.1.1.0/24',
        expected_asn: 13335,
        detected_asns: [13336],
        hijack_type: 'MOAS',
        severity: 'CRITICAL',
        description: 'Test event',
        details: { test: true },
      })

      expect(result.detected_at).toBeInstanceOf(Date)
      expect(result.created_at).toBeInstanceOf(Date)
      expect(result.resolved_at).toBeNull()
    })

    it('should correctly map database row with resolved timestamp', async () => {
      const mockRow = {
        id: 1,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: '2024-04-15T10:00:00Z',
        expected_asn: 13335,
        detected_asns: [13336],
        hijack_type: 'MOAS',
        severity: 'HIGH',
        description: 'Event',
        details: {},
        resolved: true,
        resolved_at: '2024-04-15T11:00:00Z',
        created_at: '2024-04-15T10:00:00Z',
      }

      mockPool.query.mockResolvedValue({ rows: [mockRow] })

      const result = await client.insertHijackEvent({
        asn: 13335,
        prefix: '1.1.1.0/24',
        expected_asn: 13335,
        detected_asns: [13336],
        hijack_type: 'MOAS',
        severity: 'HIGH',
        description: 'Event',
        details: {},
      })

      expect(result.resolved_at).toBeInstanceOf(Date)
    })
  })
})
