import { ROUTE_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/api/route.ts',
    lineNumber: 10,
    lineContent: 'const data = req.body.userId',
    severity: 'medium',
    category: 'missing_auth',
    title: 'Missing authentication',
    description: 'No auth check',
    confidence: 'medium',
    layer: 2,
    ...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: 'const data = req.body.userId',
    filePath: 'src/api/route.ts',
    ...overrides,
  }
}

describe('Route Scoring Rules', () => {
  const findRule = (name: string) => ROUTE_RULES.find(r => r.name === name)!

  describe('no_auth_on_route', () => {
    const rule = findRule('no_auth_on_route')

    it('fires for auth-related finding without auth on route', () => {
      const finding = makeFinding({ category: 'missing_auth' })
      const ctx = makeContext({ route: { hasAuth: false, isPublic: false, middleware: [], methods: ['GET'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(true)
      expect(rule.delta).toBe(0.15)
    })

    it('fires for sql_injection without auth', () => {
      const finding = makeFinding({ category: 'sql_injection' })
      const ctx = makeContext({ route: { hasAuth: false, isPublic: false, middleware: [], methods: ['POST'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(true)
    })

    it('does NOT fire for non-auth categories', () => {
      const finding = makeFinding({ category: 'high_entropy_string' })
      const ctx = makeContext({ route: { hasAuth: false, isPublic: false, middleware: [], methods: ['GET'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(false)
    })

    it('does NOT fire when route has auth', () => {
      const finding = makeFinding({ category: 'missing_auth' })
      const ctx = makeContext({ route: { hasAuth: true, isPublic: false, middleware: ['auth'], methods: ['GET'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(false)
    })

    it('does NOT fire when route is public', () => {
      const finding = makeFinding({ category: 'missing_auth' })
      const ctx = makeContext({ route: { hasAuth: false, isPublic: true, middleware: [], methods: ['GET'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(false)
    })

    it('does NOT fire when no route context', () => {
      const finding = makeFinding({ category: 'missing_auth' })
      const ctx = makeContext()
      expect(rule.applies(finding, ctx)).toBe(false)
    })
  })

  describe('has_auth_on_route', () => {
    const rule = findRule('has_auth_on_route')

    it('fires when route has auth middleware', () => {
      const finding = makeFinding()
      const ctx = makeContext({ route: { hasAuth: true, isPublic: false, middleware: ['authGuard'], methods: ['GET'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(true)
      expect(rule.delta).toBe(-0.15)
    })

    it('does NOT fire when route has no auth', () => {
      const finding = makeFinding()
      const ctx = makeContext({ route: { hasAuth: false, isPublic: false, middleware: [], methods: ['GET'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(false)
    })
  })

  describe('public_route', () => {
    const rule = findRule('public_route')

    it('fires for missing_auth on public route', () => {
      const finding = makeFinding({ category: 'missing_auth' })
      const ctx = makeContext({ route: { hasAuth: false, isPublic: true, middleware: [], methods: ['GET'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(true)
      expect(rule.delta).toBe(-0.25)
    })

    it('does NOT fire for non-missing_auth categories', () => {
      const finding = makeFinding({ category: 'sql_injection' })
      const ctx = makeContext({ route: { hasAuth: false, isPublic: true, middleware: [], methods: ['GET'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(false)
    })
  })

  describe('mutation_route', () => {
    const rule = findRule('mutation_route')

    it('fires for POST routes', () => {
      const finding = makeFinding()
      const ctx = makeContext({ route: { hasAuth: false, isPublic: false, middleware: [], methods: ['POST'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(true)
      expect(rule.delta).toBe(0.05)
    })

    it('fires for DELETE routes', () => {
      const finding = makeFinding()
      const ctx = makeContext({ route: { hasAuth: false, isPublic: false, middleware: [], methods: ['DELETE'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(true)
    })

    it('does NOT fire for GET routes', () => {
      const finding = makeFinding()
      const ctx = makeContext({ route: { hasAuth: false, isPublic: false, middleware: [], methods: ['GET'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(false)
    })
  })

  describe('rate_limited_route', () => {
    const rule = findRule('rate_limited_route')

    it('fires when route has rate limiting', () => {
      const finding = makeFinding()
      const ctx = makeContext({ route: { hasAuth: false, isPublic: false, middleware: [], methods: ['GET'], hasRateLimiting: true } })
      expect(rule.applies(finding, ctx)).toBe(true)
      expect(rule.delta).toBe(-0.05)
    })

    it('does NOT fire when no rate limiting', () => {
      const finding = makeFinding()
      const ctx = makeContext({ route: { hasAuth: false, isPublic: false, middleware: [], methods: ['GET'], hasRateLimiting: false } })
      expect(rule.applies(finding, ctx)).toBe(false)
    })
  })
})
