/**
 * Tests for sanitiser detection — identifying sanitisation functions in code.
 */

import { detectSanitisers, isSanitiserCall } from '../../model/sanitiser-detection'

describe('detectSanitisers', () => {
  describe('DOMPurify', () => {
    it('detects DOMPurify.sanitize when imported', () => {
      const code = `
import DOMPurify from 'dompurify'
const clean = DOMPurify.sanitize(dirty)
`
      const sanitisers = detectSanitisers(code, 'src/utils.ts')
      expect(sanitisers.some(s => s.name === 'DOMPurify.sanitize')).toBe(true)
      expect(sanitisers.find(s => s.name === 'DOMPurify.sanitize')?.sanitises).toBe('html')
      expect(sanitisers.find(s => s.name === 'DOMPurify.sanitize')?.detection).toBe('known_library')
    })

    it('does not detect DOMPurify.sanitize without import', () => {
      const code = `const clean = DOMPurify.sanitize(dirty)`
      const sanitisers = detectSanitisers(code, 'src/utils.ts')
      expect(sanitisers.some(s => s.name === 'DOMPurify.sanitize' && s.detection === 'known_library')).toBe(false)
    })
  })

  describe('validator.js', () => {
    it('detects validator.escape when imported', () => {
      const code = `
import validator from 'validator'
const safe = validator.escape(input)
`
      const sanitisers = detectSanitisers(code, 'src/utils.ts')
      expect(sanitisers.some(s => s.name === 'validator.escape')).toBe(true)
    })
  })

  describe('Zod/Joi/Yup', () => {
    it('detects zod .parse() when imported', () => {
      const code = `
import { z } from 'zod'
const schema = z.object({ name: z.string() })
const data = schema.parse(input)
`
      const sanitisers = detectSanitisers(code, 'src/validation.ts')
      expect(sanitisers.some(s => s.name === 'zod.parse' && s.sanitises === 'all')).toBe(true)
    })

    it('detects joi .validate() when imported', () => {
      const code = `
import joi from 'joi'
const result = schema.validate(input)
`
      const sanitisers = detectSanitisers(code, 'src/validation.ts')
      expect(sanitisers.some(s => s.name === 'joi.validate')).toBe(true)
    })

    it('detects yup .validate() when imported', () => {
      const code = `
import * as yup from 'yup'
const result = await schema.validate(input)
`
      const sanitisers = detectSanitisers(code, 'src/validation.ts')
      expect(sanitisers.some(s => s.name === 'yup.validate')).toBe(true)
    })
  })

  describe('Type coercion (universal)', () => {
    it('detects Number()', () => {
      const code = `const id = Number(input)`
      const sanitisers = detectSanitisers(code, 'src/api.ts')
      expect(sanitisers.some(s => s.name === 'Number' && s.sanitises === 'all')).toBe(true)
    })

    it('detects parseInt()', () => {
      const code = `const id = parseInt(input, 10)`
      const sanitisers = detectSanitisers(code, 'src/api.ts')
      expect(sanitisers.some(s => s.name === 'parseInt')).toBe(true)
    })

    it('detects parseFloat()', () => {
      const code = `const price = parseFloat(input)`
      const sanitisers = detectSanitisers(code, 'src/api.ts')
      expect(sanitisers.some(s => s.name === 'parseFloat')).toBe(true)
    })

    it('detects Boolean()', () => {
      const code = `const flag = Boolean(input)`
      const sanitisers = detectSanitisers(code, 'src/api.ts')
      expect(sanitisers.some(s => s.name === 'Boolean')).toBe(true)
    })
  })

  describe('Parameterised queries', () => {
    it('detects ? placeholder parameterised queries', () => {
      const code = `db.query('SELECT * FROM users WHERE id = ?', [userId])`
      const sanitisers = detectSanitisers(code, 'src/db.ts')
      expect(sanitisers.some(s => s.name === 'parameterised_query' && s.sanitises === 'sql')).toBe(true)
    })

    it('detects $1 placeholder parameterised queries', () => {
      const code = `db.query('SELECT * FROM users WHERE id = $1', [userId])`
      const sanitisers = detectSanitisers(code, 'src/db.ts')
      expect(sanitisers.some(s => s.name === 'parameterised_query')).toBe(true)
    })

    it('detects Sequelize replacements', () => {
      const code = `sequelize.query('SELECT * FROM users', { replacements: { id } })`
      const sanitisers = detectSanitisers(code, 'src/db.ts')
      expect(sanitisers.some(s => s.name === 'parameterised_query')).toBe(true)
    })
  })

  describe('Path sanitisers', () => {
    it('detects path.normalize()', () => {
      const code = `const safe = path.normalize(input)`
      const sanitisers = detectSanitisers(code, 'src/fs.ts')
      expect(sanitisers.some(s => s.name === 'path.normalize' && s.sanitises === 'path')).toBe(true)
    })
  })

  describe('Python sanitisers', () => {
    it('detects shlex.quote()', () => {
      const code = `safe = shlex.quote(user_input)`
      const sanitisers = detectSanitisers(code, 'app/utils.py')
      expect(sanitisers.some(s => s.name === 'shlex.quote' && s.sanitises === 'command')).toBe(true)
    })

    it('detects html.escape()', () => {
      const code = `safe = html.escape(user_input)`
      const sanitisers = detectSanitisers(code, 'app/utils.py')
      expect(sanitisers.some(s => s.name === 'html.escape' && s.sanitises === 'html')).toBe(true)
    })
  })

  describe('Name heuristic', () => {
    it('detects functions with "sanitize" in name', () => {
      const code = `const safe = sanitizeInput(dirty)`
      const sanitisers = detectSanitisers(code, 'src/utils.ts')
      expect(sanitisers.some(s => s.detection === 'name_heuristic')).toBe(true)
    })
  })

  describe('Edge cases', () => {
    it('skips comments', () => {
      const code = `// DOMPurify.sanitize(dirty)`
      const sanitisers = detectSanitisers(code, 'src/utils.ts')
      expect(sanitisers).toHaveLength(0)
    })

    it('deduplicates sanitisers on same line', () => {
      const code = `const id = Number(parseInt(input))`
      const sanitisers = detectSanitisers(code, 'src/api.ts')
      const lineOnes = sanitisers.filter(s => s.line === 1)
      const uniqueNames = new Set(lineOnes.map(s => s.name))
      expect(uniqueNames.size).toBe(lineOnes.length)
    })
  })
})

describe('isSanitiserCall', () => {
  it('detects Number() as sanitiser', () => {
    const result = isSanitiserCall('const id = Number(input)', [])
    expect(result.sanitised).toBe(true)
    expect(result.method).toBe('Number')
    expect(result.target).toBe('all')
  })

  it('detects parseInt() as sanitiser', () => {
    const result = isSanitiserCall('const id = parseInt(input, 10)', [])
    expect(result.sanitised).toBe(true)
  })

  it('returns false for non-sanitiser calls', () => {
    const result = isSanitiserCall('const data = someFunction(input)', [])
    expect(result.sanitised).toBe(false)
  })

  it('detects file-level sanitisers', () => {
    const fileSanitisers = [
      { name: 'DOMPurify.sanitize', line: 5, sanitises: 'html' as const, detection: 'known_library' as const, library: 'dompurify' }
    ]
    const result = isSanitiserCall('const safe = DOMPurify.sanitize(dirty)', fileSanitisers)
    expect(result.sanitised).toBe(true)
    expect(result.target).toBe('html')
  })
})
