/**
 * Hash Computation Tests
 */

import {
  computeFindingHash,
  computeHashFromComponents,
  normalizePathForHash,
  normalizeContentForHash,
  isValidHash,
  formatHashForDisplay,
} from '../hash'
import type { Vulnerability } from '../../types'

describe('Hash Computation', () => {
  describe('normalizePathForHash', () => {
    it('should remove leading ./', () => {
      expect(normalizePathForHash('./src/index.ts')).toBe('src/index.ts')
    })

    it('should remove leading /', () => {
      expect(normalizePathForHash('/src/index.ts')).toBe('src/index.ts')
    })

    it('should convert backslashes to forward slashes', () => {
      expect(normalizePathForHash('src\\utils\\helper.ts')).toBe('src/utils/helper.ts')
    })

    it('should lowercase the path', () => {
      expect(normalizePathForHash('Src/Index.TS')).toBe('src/index.ts')
    })

    it('should handle complex paths', () => {
      expect(normalizePathForHash('./Src\\Utils/Helper.ts')).toBe('src/utils/helper.ts')
    })
  })

  describe('normalizeContentForHash', () => {
    it('should trim whitespace', () => {
      expect(normalizeContentForHash('  const x = 1  ')).toBe('const x = 1')
    })

    it('should collapse multiple whitespace', () => {
      expect(normalizeContentForHash('const   x    =   1')).toBe('const x = 1')
    })

    it('should remove line number prefix', () => {
      expect(normalizeContentForHash('123:\tconst x = 1')).toBe('const x = 1')
    })

    it('should handle newlines', () => {
      expect(normalizeContentForHash('const x\n  = 1')).toBe('const x = 1')
    })
  })

  describe('computeFindingHash', () => {
    const mockFinding: Vulnerability = {
      id: 'test-1',
      filePath: 'src/index.ts',
      lineNumber: 10,
      lineContent: 'const secret = "api_key_12345"',
      severity: 'high',
      category: 'hardcoded_secret',
      title: 'Hardcoded Secret',
      description: 'Found a hardcoded secret',
      confidence: 'high',
      layer: 1,
    }

    it('should produce a 16-character hex hash', () => {
      const hash = computeFindingHash(mockFinding)
      expect(hash).toHaveLength(16)
      expect(/^[0-9a-f]{16}$/.test(hash)).toBe(true)
    })

    it('should produce consistent hashes for the same finding', () => {
      const hash1 = computeFindingHash(mockFinding)
      const hash2 = computeFindingHash(mockFinding)
      expect(hash1).toBe(hash2)
    })

    it('should produce different hashes for different content', () => {
      const finding1 = { ...mockFinding, lineContent: 'const a = 1' }
      const finding2 = { ...mockFinding, lineContent: 'const b = 2' }
      expect(computeFindingHash(finding1)).not.toBe(computeFindingHash(finding2))
    })

    it('should produce different hashes for different categories', () => {
      const finding1 = { ...mockFinding, category: 'hardcoded_secret' as const }
      const finding2 = { ...mockFinding, category: 'high_entropy_string' as const }
      expect(computeFindingHash(finding1)).not.toBe(computeFindingHash(finding2))
    })

    it('should produce different hashes for different file paths', () => {
      const finding1 = { ...mockFinding, filePath: 'src/a.ts' }
      const finding2 = { ...mockFinding, filePath: 'src/b.ts' }
      expect(computeFindingHash(finding1)).not.toBe(computeFindingHash(finding2))
    })

    it('should be stable across whitespace variations in content', () => {
      const finding1 = { ...mockFinding, lineContent: 'const x = 1' }
      const finding2 = { ...mockFinding, lineContent: '  const   x = 1  ' }
      expect(computeFindingHash(finding1)).toBe(computeFindingHash(finding2))
    })

    it('should be stable across path format variations', () => {
      const finding1 = { ...mockFinding, filePath: './src/index.ts' }
      const finding2 = { ...mockFinding, filePath: 'src/index.ts' }
      expect(computeFindingHash(finding1)).toBe(computeFindingHash(finding2))
    })
  })

  describe('computeHashFromComponents', () => {
    it('should produce consistent hashes', () => {
      const hash1 = computeHashFromComponents('src/index.ts', 'const x = 1', 'hardcoded_secret')
      const hash2 = computeHashFromComponents('src/index.ts', 'const x = 1', 'hardcoded_secret')
      expect(hash1).toBe(hash2)
    })

    it('should match computeFindingHash for equivalent inputs', () => {
      const finding: Vulnerability = {
        id: 'test',
        filePath: 'src/index.ts',
        lineNumber: 1,
        lineContent: 'const x = 1',
        severity: 'high',
        category: 'hardcoded_secret',
        title: 'Test',
        description: 'Test',
        confidence: 'high',
        layer: 1,
      }

      const hash1 = computeFindingHash(finding)
      const hash2 = computeHashFromComponents('src/index.ts', 'const x = 1', 'hardcoded_secret')
      expect(hash1).toBe(hash2)
    })
  })

  describe('isValidHash', () => {
    it('should accept valid 16-char hex hashes', () => {
      expect(isValidHash('0123456789abcdef')).toBe(true)
      expect(isValidHash('ABCDEF0123456789')).toBe(true)
      expect(isValidHash('aAbBcCdDeEfF0011')).toBe(true)
    })

    it('should reject invalid hashes', () => {
      expect(isValidHash('')).toBe(false)
      expect(isValidHash('tooshort')).toBe(false)
      expect(isValidHash('0123456789abcdefghij')).toBe(false) // too long
      expect(isValidHash('0123456789abcdeg')).toBe(false) // invalid char 'g'
      expect(isValidHash('0123456789abcde!')).toBe(false) // invalid char '!'
    })
  })

  describe('formatHashForDisplay', () => {
    it('should return short hashes unchanged', () => {
      expect(formatHashForDisplay('abcd1234')).toBe('abcd1234')
      expect(formatHashForDisplay('0123456789abcdef')).toBe('0123456789abcdef')
    })

    it('should truncate long hashes', () => {
      expect(formatHashForDisplay('0123456789abcdef0123')).toBe('0123456789ab...')
    })
  })
})
