/**
 * Tests for Function Classifier — classifyExportedFunctions, buildFunctionProfiles
 */

import { classifyExportedFunctions, buildFunctionProfiles } from '../../model/function-classifier'
import { buildModuleGraph } from '../../model/module-graph'
import { extractExports } from '../../model/import-resolver'
import type { ScanFile } from '../../shared/types'

function makeFile(path: string, content: string): ScanFile {
  return { path, content, language: 'typescript' }
}

describe('classifyExportedFunctions', () => {
  it('detects SQL query sinks in exported function', () => {
    const content = `export function getUser(id: string) {
  return db.query('SELECT * FROM users WHERE id = ' + id)
}`
    const exports = extractExports(content, 'src/db.ts')
    const profiles = classifyExportedFunctions(content, 'src/db.ts', exports)

    expect(profiles).toHaveLength(1)
    expect(profiles[0].name).toBe('getUser')
    expect(profiles[0].reachesSinks).toContain('sql_query')
    expect(profiles[0].paramNames).toContain('id')
    expect(profiles[0].dangerousParams).toContain('id')
  })

  it('detects eval sinks', () => {
    const content = `export function runCode(code: string) {
  return eval(code)
}`
    const exports = extractExports(content, 'src/runner.ts')
    const profiles = classifyExportedFunctions(content, 'src/runner.ts', exports)

    expect(profiles).toHaveLength(1)
    expect(profiles[0].reachesSinks).toContain('eval')
    expect(profiles[0].dangerousParams).toContain('code')
  })

  it('detects exec/command sinks', () => {
    const content = `export function runCommand(cmd: string) {
  exec(cmd)
}`
    const exports = extractExports(content, 'src/exec.ts')
    const profiles = classifyExportedFunctions(content, 'src/exec.ts', exports)

    expect(profiles).toHaveLength(1)
    expect(profiles[0].reachesSinks).toContain('exec')
  })

  it('detects functions that return user data', () => {
    const content = `export function getUserInput(req) {
  return req.body
}`
    const exports = extractExports(content, 'src/input.ts')
    const profiles = classifyExportedFunctions(content, 'src/input.ts', exports)

    expect(profiles).toHaveLength(1)
    expect(profiles[0].returnsUserData).toBe(true)
  })

  it('detects sanitisation in function body', () => {
    const content = `export function getUser(id: string) {
  const safeId = parseInt(id)
  return db.query('SELECT * FROM users WHERE id = ' + safeId)
}`
    const exports = extractExports(content, 'src/db.ts')
    const profiles = classifyExportedFunctions(content, 'src/db.ts', exports)

    expect(profiles).toHaveLength(1)
    expect(profiles[0].hasSanitisation).toBe(true)
  })

  it('skips functions without sinks or user data returns', () => {
    const content = `export function add(a: number, b: number) {
  return a + b
}`
    const exports = extractExports(content, 'src/math.ts')
    const profiles = classifyExportedFunctions(content, 'src/math.ts', exports)

    expect(profiles).toHaveLength(0)
  })

  it('skips re-exports', () => {
    const content = `export { getUser } from './db'`
    const exports = extractExports(content, 'src/index.ts')
    const profiles = classifyExportedFunctions(content, 'src/index.ts', exports)

    expect(profiles).toHaveLength(0)
  })

  it('handles multiple dangerous params', () => {
    const content = `export function search(table: string, query: string) {
  return db.query('SELECT * FROM ' + table + ' WHERE name = ' + query)
}`
    const exports = extractExports(content, 'src/db.ts')
    const profiles = classifyExportedFunctions(content, 'src/db.ts', exports)

    expect(profiles).toHaveLength(1)
    expect(profiles[0].dangerousParams).toContain('table')
    expect(profiles[0].dangerousParams).toContain('query')
  })

  it('detects multiple sink types in one function', () => {
    const content = `export function dangerous(input: string) {
  db.query(input)
  exec(input)
}`
    const exports = extractExports(content, 'src/danger.ts')
    const profiles = classifyExportedFunctions(content, 'src/danger.ts', exports)

    expect(profiles).toHaveLength(1)
    expect(profiles[0].reachesSinks).toContain('sql_query')
    expect(profiles[0].reachesSinks).toContain('exec')
  })
})

describe('buildFunctionProfiles', () => {
  it('only profiles files that are imported by others', () => {
    const files = [
      makeFile('src/routes.ts', `import { getUser } from './db'\nexport async function GET() { getUser('1') }`),
      makeFile('src/db.ts', `export function getUser(id) { return db.query('SELECT * FROM users WHERE id = ' + id) }`),
      makeFile('src/standalone.ts', `export function helper() { return db.query('SELECT 1') }`),
    ]

    const graph = buildModuleGraph(files)
    const profiles = buildFunctionProfiles(files, graph)

    // db.ts is imported by routes.ts, so it should be profiled
    expect(profiles.has('src/db.ts')).toBe(true)
    // standalone.ts is not imported by anyone, so it should NOT be profiled
    expect(profiles.has('src/standalone.ts')).toBe(false)
  })

  it('returns empty map when no files are imported', () => {
    const files = [
      makeFile('src/a.ts', `export const a = 1`),
      makeFile('src/b.ts', `export const b = 2`),
    ]

    const graph = buildModuleGraph(files)
    const profiles = buildFunctionProfiles(files, graph)
    expect(profiles.size).toBe(0)
  })
})
