/**
 * Tests for taint adjustment rules in the confidence scoring system.
 */

import { TAINT_RULES } from '../../score/adjustments'
import type { AdjustmentContext } from '../../score/types'
import type { Vulnerability } from '../../shared/types'

function makeFinding(overrides: Partial<Vulnerability> = {}): Vulnerability {
  return {
    id: 'test-1',
    filePath: 'src/app.ts',
    lineNumber: 10,
    lineContent: 'db.query(userInput)',
    severity: 'medium',
    category: 'dangerous_function',
    title: 'Test finding',
    description: 'Test',
    confidence: 'medium',
    layer: 2,
    baseConfidence: 0.50,
    ...overrides,
  }
}

function makeContext(overrides: Partial<AdjustmentContext> = {}): AdjustmentContext {
  return {
    file: {
      isTestFile: false,
      isExampleFile: false,
      isDocFile: false,
      isScannerCode: false,
      isToolingDir: false,
      isServerOnly: false,
      isClientBundled: false,
    },
    project: {
      hasAuthMiddleware: false,
      hasTypeScript: true,
      orm: null,
      validationLib: null,
    },
    lineContent: 'db.query(userInput)',
    filePath: 'src/app.ts',
    ...overrides,
  }
}

function findRule(name: string) {
  return TAINT_RULES.find(r => r.name === name)
}

describe('Taint Adjustment Rules', () => {
  it('has exactly 4 rules', () => {
    expect(TAINT_RULES).toHaveLength(4)
  })

  describe('confirmed_taint_unsanitised', () => {
    const rule = findRule('confirmed_taint_unsanitised')!

    it('exists with correct delta', () => {
      expect(rule).toBeDefined()
      expect(rule.delta).toBe(0.35)
      expect(rule.source).toBe('taint')
    })

    it('applies when taint is confirmed, unsanitised, and not low confidence', () => {
      const finding = makeFinding()
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['http_body'], sinkType: 'sql_query', sanitised: false, confidence: 'high' },
      })
      expect(rule.applies(finding, ctx)).toBe(true)
    })

    it('applies with medium confidence', () => {
      const finding = makeFinding()
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['http_body'], sinkType: 'sql_query', sanitised: false, confidence: 'medium' },
      })
      expect(rule.applies(finding, ctx)).toBe(true)
    })

    it('does not apply when sanitised', () => {
      const finding = makeFinding()
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['http_body'], sinkType: 'sql_query', sanitised: true, confidence: 'high' },
      })
      expect(rule.applies(finding, ctx)).toBe(false)
    })

    it('does not apply with low confidence', () => {
      const finding = makeFinding()
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['cli_args'], sinkType: 'sql_query', sanitised: false, confidence: 'low' },
      })
      expect(rule.applies(finding, ctx)).toBe(false)
    })

    it('does not apply when taint is undefined', () => {
      const finding = makeFinding()
      const ctx = makeContext()
      expect(rule.applies(finding, ctx)).toBe(false)
    })
  })

  describe('confirmed_taint_sanitised', () => {
    const rule = findRule('confirmed_taint_sanitised')!

    it('exists with correct delta', () => {
      expect(rule).toBeDefined()
      expect(rule.delta).toBe(0.15)
    })

    it('applies when taint is confirmed and sanitised', () => {
      const finding = makeFinding()
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['http_body'], sinkType: 'sql_query', sanitised: true, confidence: 'high' },
      })
      expect(rule.applies(finding, ctx)).toBe(true)
    })

    it('does not apply when not sanitised', () => {
      const finding = makeFinding()
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['http_body'], sinkType: 'sql_query', sanitised: false, confidence: 'high' },
      })
      expect(rule.applies(finding, ctx)).toBe(false)
    })
  })

  describe('no_taint_path', () => {
    const rule = findRule('no_taint_path')!

    it('exists with correct delta', () => {
      expect(rule).toBeDefined()
      expect(rule.delta).toBe(-0.10)
    })

    it('applies for sink-relevant categories when taint is not confirmed', () => {
      const finding = makeFinding({ category: 'dangerous_function' })
      const ctx = makeContext({
        taint: { confirmed: false, sources: [], sinkType: null, sanitised: false, confidence: null },
      })
      expect(rule.applies(finding, ctx)).toBe(true)
    })

    it('applies for sql_injection category', () => {
      const finding = makeFinding({ category: 'sql_injection' })
      const ctx = makeContext({
        taint: { confirmed: false, sources: [], sinkType: null, sanitised: false, confidence: null },
      })
      expect(rule.applies(finding, ctx)).toBe(true)
    })

    it('applies for xss category', () => {
      const finding = makeFinding({ category: 'xss' })
      const ctx = makeContext({
        taint: { confirmed: false, sources: [], sinkType: null, sanitised: false, confidence: null },
      })
      expect(rule.applies(finding, ctx)).toBe(true)
    })

    it('does not apply for non-sink categories', () => {
      const finding = makeFinding({ category: 'hardcoded_secret' })
      const ctx = makeContext({
        taint: { confirmed: false, sources: [], sinkType: null, sanitised: false, confidence: null },
      })
      expect(rule.applies(finding, ctx)).toBe(false)
    })

    it('does not apply when taint is confirmed', () => {
      const finding = makeFinding({ category: 'dangerous_function' })
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['http_body'], sinkType: 'sql_query', sanitised: false, confidence: 'high' },
      })
      expect(rule.applies(finding, ctx)).toBe(false)
    })

    it('does not apply when taint is undefined (unanalyzed file)', () => {
      const finding = makeFinding({ category: 'dangerous_function' })
      const ctx = makeContext()
      expect(rule.applies(finding, ctx)).toBe(false)
    })
  })

  describe('low_confidence_taint', () => {
    const rule = findRule('low_confidence_taint')!

    it('exists with correct delta', () => {
      expect(rule).toBeDefined()
      expect(rule.delta).toBe(0.10)
    })

    it('applies for confirmed, unsanitised, low-confidence taint', () => {
      const finding = makeFinding()
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['cli_args'], sinkType: 'sql_query', sanitised: false, confidence: 'low' },
      })
      expect(rule.applies(finding, ctx)).toBe(true)
    })

    it('does not apply for high confidence', () => {
      const finding = makeFinding()
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['http_body'], sinkType: 'sql_query', sanitised: false, confidence: 'high' },
      })
      expect(rule.applies(finding, ctx)).toBe(false)
    })

    it('does not apply when sanitised', () => {
      const finding = makeFinding()
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['cli_args'], sinkType: 'sql_query', sanitised: true, confidence: 'low' },
      })
      expect(rule.applies(finding, ctx)).toBe(false)
    })
  })

  describe('Rule mutual exclusivity', () => {
    it('confirmed_taint_unsanitised and low_confidence_taint do not both apply for high confidence', () => {
      const finding = makeFinding()
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['http_body'], sinkType: 'sql_query', sanitised: false, confidence: 'high' },
      })
      const unsanitised = findRule('confirmed_taint_unsanitised')!
      const lowConf = findRule('low_confidence_taint')!

      expect(unsanitised.applies(finding, ctx)).toBe(true)
      expect(lowConf.applies(finding, ctx)).toBe(false)
    })

    it('only low_confidence_taint applies for low confidence unsanitised', () => {
      const finding = makeFinding()
      const ctx = makeContext({
        taint: { confirmed: true, sources: ['cli_args'], sinkType: 'sql_query', sanitised: false, confidence: 'low' },
      })
      const unsanitised = findRule('confirmed_taint_unsanitised')!
      const lowConf = findRule('low_confidence_taint')!

      expect(unsanitised.applies(finding, ctx)).toBe(false)
      expect(lowConf.applies(finding, ctx)).toBe(true)
    })
  })
})
