import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { RetryScheduler } from '../retry-scheduler'
import type { HijackAlertsDatabaseClient } from '../db-client'
import type { WebhookClient } from '../webhook-client'
import type { HijackEvent, WebhookSubscription, WebhookDelivery } from '../types'

describe('RetryScheduler', () => {
  let scheduler: RetryScheduler
  let mockDbClient: any
  let mockWebhookClient: any

  beforeEach(() => {
    vi.clearAllMocks()
    mockDbClient = {
      getFailedDeliveriesForRetry: vi.fn(),
      recordWebhookDelivery: vi.fn(),
      updateWebhookLastTriggered: vi.fn(),
      updateWebhookStatus: vi.fn(),
    }

    mockWebhookClient = {
      sendWebhook: vi.fn(),
    }

    scheduler = new RetryScheduler(mockDbClient, mockWebhookClient)
  })

  afterEach(() => {
    scheduler.stop()
  })

  describe('scheduler lifecycle', () => {
    it('should start scheduler without errors', () => {
      expect(() => scheduler.start()).not.toThrow()
    })

    it('should stop scheduler without errors', () => {
      scheduler.start()
      expect(() => scheduler.stop()).not.toThrow()
    })

    it('should prevent double start', () => {
      scheduler.start()
      const firstStart = (scheduler as any).cronJob
      scheduler.start()
      const secondStart = (scheduler as any).cronJob

      expect(firstStart).toBe(secondStart)
    })

    it('should allow restart after stop', () => {
      scheduler.start()
      scheduler.stop()
      expect((scheduler as any).cronJob).toBeNull()
      expect(() => scheduler.start()).not.toThrow()
    })
  })

  describe('processFailedDeliveries', () => {
    it('should process empty failed deliveries list', async () => {
      mockDbClient.getFailedDeliveriesForRetry.mockResolvedValue([])

      await (scheduler as any).processFailedDeliveries()

      expect(mockDbClient.recordWebhookDelivery).not.toHaveBeenCalled()
      expect(mockWebhookClient.sendWebhook).not.toHaveBeenCalled()
    })

    it('should retry failed webhook delivery successfully', async () => {
      const mockEvent: HijackEvent = {
        id: 1,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: new Date('2024-04-15T10:00:00Z'),
        expected_asn: 13335,
        detected_asns: [13336],
        hijack_type: 'MOAS',
        severity: 'CRITICAL',
        description: 'MOAS detected',
        details: {},
        resolved: false,
        resolved_at: null,
        created_at: new Date('2024-04-15T10:00:00Z'),
      }

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

      const mockDelivery: WebhookDelivery = {
        id: 1,
        subscription_id: 1,
        event_id: 1,
        attempt_number: 1,
        sent_at: new Date('2024-04-15T10:00:00Z'),
        response_status: 500,
        response_body: null,
        error_message: 'Server error',
        next_retry_at: new Date('2024-04-15T10:02:00Z'),
      }

      mockDbClient.getFailedDeliveriesForRetry.mockResolvedValue([
        {
          webhook: mockWebhook,
          event: mockEvent,
          delivery: mockDelivery,
        },
      ])

      mockWebhookClient.sendWebhook.mockResolvedValue({
        success: true,
        status: 200,
        error: null,
      })

      await (scheduler as any).processFailedDeliveries()

      expect(mockWebhookClient.sendWebhook).toHaveBeenCalledWith(
        mockEvent,
        'https://example.com/webhook',
        'sk_test_123',
        10000
      )
      expect(mockDbClient.recordWebhookDelivery).toHaveBeenCalledWith(
        1,
        1,
        2,
        200,
        null,
        null
      )
      expect(mockDbClient.updateWebhookLastTriggered).toHaveBeenCalledWith(1, 0)
    })

    it('should increment failure count on retry failure', async () => {
      const mockEvent: HijackEvent = {
        id: 1,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: new Date('2024-04-15T10:00:00Z'),
        expected_asn: 13335,
        detected_asns: [13336],
        hijack_type: 'MOAS',
        severity: 'HIGH',
        description: 'Event',
        details: {},
        resolved: false,
        resolved_at: null,
        created_at: new Date('2024-04-15T10:00:00Z'),
      }

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

      const mockDelivery: WebhookDelivery = {
        id: 1,
        subscription_id: 1,
        event_id: 1,
        attempt_number: 1,
        sent_at: new Date('2024-04-15T10:00:00Z'),
        response_status: 500,
        response_body: null,
        error_message: 'Server error',
        next_retry_at: new Date('2024-04-15T10:02:00Z'),
      }

      mockDbClient.getFailedDeliveriesForRetry.mockResolvedValue([
        {
          webhook: mockWebhook,
          event: mockEvent,
          delivery: mockDelivery,
        },
      ])

      mockWebhookClient.sendWebhook.mockResolvedValue({
        success: false,
        status: 500,
        error: 'Server error',
      })

      await (scheduler as any).processFailedDeliveries()

      expect(mockDbClient.recordWebhookDelivery).toHaveBeenCalledWith(
        1,
        1,
        2,
        500,
        null,
        'Server error'
      )
      expect(mockDbClient.updateWebhookLastTriggered).toHaveBeenCalledWith(1, 2)
    })

    it('should disable webhook after max retries exceeded', async () => {
      const mockEvent: HijackEvent = {
        id: 1,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: new Date('2024-04-15T10:00:00Z'),
        expected_asn: 13335,
        detected_asns: [13336],
        hijack_type: 'MOAS',
        severity: 'HIGH',
        description: 'Event',
        details: {},
        resolved: false,
        resolved_at: null,
        created_at: new Date('2024-04-15T10:00:00Z'),
      }

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

      const mockDelivery: WebhookDelivery = {
        id: 1,
        subscription_id: 1,
        event_id: 1,
        attempt_number: 2,
        sent_at: new Date('2024-04-15T10:00:00Z'),
        response_status: 500,
        response_body: null,
        error_message: 'Server error',
        next_retry_at: new Date('2024-04-15T10:02:00Z'),
      }

      mockDbClient.getFailedDeliveriesForRetry.mockResolvedValue([
        {
          webhook: mockWebhook,
          event: mockEvent,
          delivery: mockDelivery,
        },
      ])

      mockWebhookClient.sendWebhook.mockResolvedValue({
        success: false,
        status: 500,
        error: 'Server error',
      })

      await (scheduler as any).processFailedDeliveries()

      expect(mockDbClient.updateWebhookStatus).toHaveBeenCalledWith(1, false)
    })

    it('should process multiple failed deliveries', async () => {
      const mockEvent1: HijackEvent = {
        id: 1,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: new Date('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: new Date('2024-04-15T10:00:00Z'),
      }

      const mockEvent2: HijackEvent = {
        id: 2,
        asn: 15169,
        prefix: '8.8.8.0/24',
        detected_at: new Date('2024-04-15T10:00:00Z'),
        expected_asn: 15169,
        detected_asns: [15170],
        hijack_type: 'HIJACK',
        severity: 'CRITICAL',
        description: 'Event 2',
        details: {},
        resolved: false,
        resolved_at: null,
        created_at: new Date('2024-04-15T10:00:00Z'),
      }

      const mockWebhook1: WebhookSubscription = {
        id: 1,
        asn: 13335,
        endpoint_url: 'https://example.com/webhook1',
        secret_key: 'sk_test_1',
        created_at: new Date('2024-04-15T10:00:00Z'),
        last_triggered_at: null,
        failure_count: 1,
        active: true,
        max_retries: 3,
        timeout_ms: 10000,
        metadata: {},
      }

      const mockWebhook2: WebhookSubscription = {
        id: 2,
        asn: 15169,
        endpoint_url: 'https://example.com/webhook2',
        secret_key: 'sk_test_2',
        created_at: new Date('2024-04-15T10:00:00Z'),
        last_triggered_at: null,
        failure_count: 2,
        active: true,
        max_retries: 3,
        timeout_ms: 10000,
        metadata: {},
      }

      const mockDelivery1: WebhookDelivery = {
        id: 1,
        subscription_id: 1,
        event_id: 1,
        attempt_number: 1,
        sent_at: new Date('2024-04-15T10:00:00Z'),
        response_status: 500,
        response_body: null,
        error_message: 'Error 1',
        next_retry_at: new Date('2024-04-15T10:02:00Z'),
      }

      const mockDelivery2: WebhookDelivery = {
        id: 2,
        subscription_id: 2,
        event_id: 2,
        attempt_number: 1,
        sent_at: new Date('2024-04-15T10:00:00Z'),
        response_status: 502,
        response_body: null,
        error_message: 'Error 2',
        next_retry_at: new Date('2024-04-15T10:02:00Z'),
      }

      mockDbClient.getFailedDeliveriesForRetry.mockResolvedValue([
        { webhook: mockWebhook1, event: mockEvent1, delivery: mockDelivery1 },
        { webhook: mockWebhook2, event: mockEvent2, delivery: mockDelivery2 },
      ])

      mockWebhookClient.sendWebhook.mockResolvedValue({
        success: true,
        status: 200,
        error: null,
      })

      await (scheduler as any).processFailedDeliveries()

      expect(mockWebhookClient.sendWebhook).toHaveBeenCalledTimes(2)
      expect(mockDbClient.recordWebhookDelivery).toHaveBeenCalledTimes(2)
      expect(mockDbClient.updateWebhookLastTriggered).toHaveBeenCalledTimes(2)
    })

    it('should handle errors in webhook processing', async () => {
      const mockEvent: HijackEvent = {
        id: 1,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: new Date('2024-04-15T10:00:00Z'),
        expected_asn: 13335,
        detected_asns: [13336],
        hijack_type: 'MOAS',
        severity: 'HIGH',
        description: 'Event',
        details: {},
        resolved: false,
        resolved_at: null,
        created_at: new Date('2024-04-15T10:00:00Z'),
      }

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

      const mockDelivery: WebhookDelivery = {
        id: 1,
        subscription_id: 1,
        event_id: 1,
        attempt_number: 1,
        sent_at: new Date('2024-04-15T10:00:00Z'),
        response_status: 500,
        response_body: null,
        error_message: 'Error',
        next_retry_at: new Date('2024-04-15T10:02:00Z'),
      }

      mockDbClient.getFailedDeliveriesForRetry.mockResolvedValue([
        { webhook: mockWebhook, event: mockEvent, delivery: mockDelivery },
      ])

      mockWebhookClient.sendWebhook.mockRejectedValue(new Error('Network error'))

      await expect(
        (scheduler as any).processFailedDeliveries()
      ).rejects.toThrow()
    })

    it('should respect webhook timeout setting', async () => {
      const mockEvent: HijackEvent = {
        id: 1,
        asn: 13335,
        prefix: '1.1.1.0/24',
        detected_at: new Date('2024-04-15T10:00:00Z'),
        expected_asn: 13335,
        detected_asns: [13336],
        hijack_type: 'MOAS',
        severity: 'HIGH',
        description: 'Event',
        details: {},
        resolved: false,
        resolved_at: null,
        created_at: new Date('2024-04-15T10:00:00Z'),
      }

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

      const mockDelivery: WebhookDelivery = {
        id: 1,
        subscription_id: 1,
        event_id: 1,
        attempt_number: 1,
        sent_at: new Date('2024-04-15T10:00:00Z'),
        response_status: 500,
        response_body: null,
        error_message: 'Error',
        next_retry_at: new Date('2024-04-15T10:02:00Z'),
      }

      mockDbClient.getFailedDeliveriesForRetry.mockResolvedValue([
        { webhook: mockWebhook, event: mockEvent, delivery: mockDelivery },
      ])

      mockWebhookClient.sendWebhook.mockResolvedValue({
        success: true,
        status: 200,
        error: null,
      })

      await (scheduler as any).processFailedDeliveries()

      expect(mockWebhookClient.sendWebhook).toHaveBeenCalledWith(
        expect.any(Object),
        expect.any(String),
        expect.any(String),
        5000
      )
    })
  })
})
