import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { checkForHijacks, classifyHijack, enrichHijackDescriptionWithOllama } from '../detector'
import type { HijackAlertsDatabaseClient } from '../db-client'

describe('Hijack Detector', () => {
  let mockDbClient: Partial<HijackAlertsDatabaseClient>

  beforeEach(() => {
    mockDbClient = {
      getRecentHijackEvent: vi.fn().mockResolvedValue(null),
    }
    vi.clearAllMocks()
  })

  afterEach(() => {
    vi.clearAllMocks()
  })

  describe('checkForHijacks', () => {
    it('should return error for invalid ASN:prefix format', async () => {
      const result = await checkForHijacks('invalid-format', mockDbClient as HijackAlertsDatabaseClient)
      expect(result[0]?.detected).toBe(false)
      expect(result[0]?.reason).toContain('Invalid ASN:prefix format')
    })

    it('should return error for non-numeric ASN', async () => {
      const result = await checkForHijacks('abc:185.1.0.0/24', mockDbClient as HijackAlertsDatabaseClient)
      expect(result[0]?.detected).toBe(false)
      expect(result[0]?.reason).toContain('Invalid ASN number')
    })

    it('should check for recent events to avoid duplicates', async () => {
      mockDbClient.getRecentHijackEvent = vi.fn().mockResolvedValue({
        id: 1,
        asn: 13335,
        prefix: '185.1.0.0/24',
      })

      const result = await checkForHijacks('13335:185.1.0.0/24', mockDbClient as HijackAlertsDatabaseClient)

      expect(mockDbClient.getRecentHijackEvent).toHaveBeenCalledWith(13335, '185.1.0.0/24', 6)
      expect(result[0]?.detected).toBe(false)
      expect(result[0]?.reason).toContain('Recently detected')
    })

    it('should return non-detected for valid input with no hijack', async () => {
      const result = await checkForHijacks('13335:185.1.0.0/24', mockDbClient as HijackAlertsDatabaseClient)
      expect(result[0]?.detected).toBe(false)
      expect(result[0]?.reason).toBe('No hijack detected')
    })

    it('should parse ASN and prefix correctly', async () => {
      const result = await checkForHijacks('15169:8.8.8.0/24', mockDbClient as HijackAlertsDatabaseClient)
      expect(mockDbClient.getRecentHijackEvent).toHaveBeenCalledWith(15169, '8.8.8.0/24', expect.any(Number))
    })

    it('should handle deduplication with various time windows', async () => {
      mockDbClient.getRecentHijackEvent = vi.fn().mockResolvedValue(null)

      const result = await checkForHijacks('13335:185.1.0.0/24', mockDbClient as HijackAlertsDatabaseClient)
      expect(result[0]?.detected).toBe(false)

      expect(mockDbClient.getRecentHijackEvent).toHaveBeenCalledWith(13335, '185.1.0.0/24', 6)
    })
  })

  describe('classifyHijack', () => {
    it('should classify MOAS when expected ASN is in detected list with multiple origins', () => {
      const result = classifyHijack(13335, [13335, 8451], '185.1.0.0/24')

      expect(result.hijack_type).toBe('MOAS')
      expect(result.severity).toBe('MEDIUM')
      expect(result.baseDescription).toContain('Multiple Origin AS')
    })

    it('should classify HIJACK when completely unauthorized ASN detected', () => {
      const result = classifyHijack(13335, [64512], '185.1.0.0/24')

      expect(result.hijack_type).toBe('HIJACK')
      expect(result.severity).toBe('CRITICAL') // /24 prefix
      expect(result.baseDescription).toContain('Unauthorized ASN')
    })

    it('should classify LEAK for private ASN prefix leak', () => {
      const result = classifyHijack(65000, [65001], '10.0.0.0/8')

      expect(result.hijack_type).toBe('LEAK')
      expect(result.severity).toBe('MEDIUM')
      expect(result.baseDescription).toContain('prefix leak')
    })

    it('should set CRITICAL severity for /24 prefix hijack', () => {
      const result = classifyHijack(13335, [64512], '185.1.0.0/24')

      expect(result.severity).toBe('CRITICAL')
    })

    it('should set HIGH severity for /16 prefix hijack', () => {
      const result = classifyHijack(13335, [64512], '185.1.0.0/16')

      expect(result.severity).toBe('HIGH')
    })

    it('should handle empty detected_asns array', () => {
      const result = classifyHijack(13335, [], '185.1.0.0/24')

      expect(result.hijack_type).toBe('HIJACK')
      expect(result.severity).toBe('HIGH')
      expect(result.baseDescription).toContain('no origin ASN detected')
    })

    it('should classify hijack for /32 (most specific)', () => {
      const result = classifyHijack(13335, [64512], '1.2.3.4/32')

      expect(result.hijack_type).toBe('HIJACK')
      expect(result.severity).toBe('CRITICAL')
    })

    it('should classify hijack for /8 as HIGH (less specific)', () => {
      const result = classifyHijack(13335, [64512], '185.0.0.0/8')

      expect(result.hijack_type).toBe('HIJACK')
      expect(result.severity).toBe('HIGH')
    })

    it('should handle prefix without CIDR notation', () => {
      const result = classifyHijack(13335, [64512], '185.1.0.0')

      expect(result.hijack_type).toBe('HIJACK')
      expect(result.severity).toBe('HIGH') // No /X means prefixLength = 0
    })
  })

  describe('enrichHijackDescriptionWithOllama', () => {
    afterEach(() => {
      vi.clearAllMocks()
    })

    it('should return base description when Ollama is unavailable', async () => {
      const baseDesc = 'Unauthorized ASN detected'
      const result = await enrichHijackDescriptionWithOllama(baseDesc, '185.1.0.0/24', [64512])

      expect(result).toBe(baseDesc)
    })

    it('should return enriched description when Ollama responds', async () => {
      const baseDesc = 'Unauthorized ASN detected'
      const enrichedText = 'This is a critical security incident.'

      global.fetch = vi.fn().mockResolvedValue({
        ok: true,
        json: async () => ({ response: enrichedText })
      })

      const result = await enrichHijackDescriptionWithOllama(baseDesc, '185.1.0.0/24', [64512])

      expect(result).toContain(baseDesc)
      expect(result).toContain('AI Analysis:')
      expect(result).toContain(enrichedText)
    })

    it('should fallback to base description on fetch error', async () => {
      const baseDesc = 'Unauthorized ASN detected'

      global.fetch = vi.fn().mockRejectedValue(new Error('Network error'))

      const result = await enrichHijackDescriptionWithOllama(baseDesc, '185.1.0.0/24', [64512])

      expect(result).toBe(baseDesc)
    })

    it('should fallback on Ollama HTTP error', async () => {
      const baseDesc = 'Unauthorized ASN detected'

      global.fetch = vi.fn().mockResolvedValue({
        ok: false,
        status: 500
      })

      const result = await enrichHijackDescriptionWithOllama(baseDesc, '185.1.0.0/24', [64512])

      expect(result).toBe(baseDesc)
    })

    it('should timeout after 5 seconds', async () => {
      const baseDesc = 'Unauthorized ASN detected'

      global.fetch = vi.fn().mockImplementation((url: string, options: RequestInit) => {
        return new Promise((resolve, reject) => {
          const abortHandler = () => {
            reject(new DOMException('The operation was aborted.', 'AbortError'))
          }

          const timeoutId = setTimeout(() => {
            resolve({ ok: true, json: async () => ({ response: 'slow response' }) })
          }, 10000)

          // Listen for abort signal
          if (options.signal instanceof AbortSignal) {
            options.signal.addEventListener('abort', () => {
              clearTimeout(timeoutId)
              abortHandler()
            })
          }
        })
      })

      const startTime = Date.now()
      const result = await enrichHijackDescriptionWithOllama(baseDesc, '185.1.0.0/24', [64512])
      const elapsed = Date.now() - startTime

      expect(result).toBe(baseDesc)
      // AbortController timeout should trigger around 5-6 seconds
      expect(elapsed).toBeGreaterThanOrEqual(5000)
      expect(elapsed).toBeLessThan(7000)
    }, { timeout: 10000 })

    it('should use custom OLLAMA_URL from env', async () => {
      const baseDesc = 'Test'
      const customUrl = 'http://custom-ollama:11434'
      process.env.OLLAMA_URL = customUrl

      global.fetch = vi.fn().mockResolvedValue({
        ok: true,
        json: async () => ({ response: 'enriched' })
      })

      await enrichHijackDescriptionWithOllama(baseDesc, '185.1.0.0/24', [64512])

      expect(global.fetch).toHaveBeenCalledWith(
        expect.stringContaining(customUrl),
        expect.any(Object)
      )

      delete process.env.OLLAMA_URL
    })

    it('should handle empty Ollama response', async () => {
      const baseDesc = 'Unauthorized ASN detected'

      global.fetch = vi.fn().mockResolvedValue({
        ok: true,
        json: async () => ({})
      })

      const result = await enrichHijackDescriptionWithOllama(baseDesc, '185.1.0.0/24', [64512])

      expect(result).toBe(baseDesc)
    })
  })
})
