/**
 * Tests for the modified request builder with context extraction support.
 *
 * Verifies:
 * - Scoped mode extracts relevant regions for large files
 * - Scoped mode sends full content for small files
 * - Full mode always sends all lines
 * - Default mode uses scoped
 * - Multi-file builder works correctly
 */

import {
  buildHighContextValidationRequest,
  buildMultiFileValidationRequest,
  buildFileTaintSummary,
  buildFindingTaintAnnotation,
} from '../request-builder'
import type { ScanFile, Vulnerability } from '../../shared/types'
import type { ProjectContext } from '../../model/project-context'
import type { FileTaintAnalysis } from '../../model/taint-types'

// ============================================================================
// Test Fixtures
// ============================================================================

function createMockFile(lineCount: number, path = 'src/test.ts'): ScanFile {
  const content = Array.from({ length: lineCount }, (_, i) => `const line${i + 1} = 'value${i + 1}'`).join('\n')
  return {
    path,
    content,
    language: 'typescript',
    size: content.length,
  }
}

function createMockFinding(lineNumber: number, category = 'hardcoded_secret' as const): Vulnerability {
  return {
    id: `test-${lineNumber}`,
    filePath: 'src/test.ts',
    lineNumber,
    lineContent: `const line${lineNumber} = 'value${lineNumber}'`,
    severity: 'high',
    category,
    title: 'Test Finding',
    description: 'Test description',
    confidence: 'high',
    layer: 1,
  }
}

function createMockProjectContext(): ProjectContext {
  return {
    summary: 'Test project context',
    auth: {
      hasGlobalMiddleware: false,
      authProvider: null,
      protectedPaths: [],
      publicPaths: [],
      throwingHelpers: [],
    },
    dataAccess: {
      orm: null,
      hasRLS: false,
    },
    frameworks: {
      primary: null,
    },
    secrets: {
      hasEnvConfig: false,
      secretPatterns: [],
    },
  } as any
}

// ============================================================================
// buildHighContextValidationRequest
// ============================================================================

describe('buildHighContextValidationRequest', () => {
  test('scoped mode with large file shows omission markers', () => {
    const file = createMockFile(300)
    const findings = [createMockFinding(150)]
    const context = createMockProjectContext()

    const result = buildHighContextValidationRequest(file, findings, context, {
      contextMode: 'scoped',
    })

    expect(result).toContain('lines omitted')
    expect(result).toContain('Showing')
    expect(result).toContain('relevant regions around findings')
    expect(result).toContain(`const line150 = 'value150'`)
  })

  test('scoped mode with small file sends full content', () => {
    const file = createMockFile(50)
    const findings = [createMockFinding(25)]
    const context = createMockProjectContext()

    const result = buildHighContextValidationRequest(file, findings, context, {
      contextMode: 'scoped',
    })

    // Small file should not have omission markers
    expect(result).not.toContain('lines omitted')
    // Should contain first and last lines
    expect(result).toContain(`const line1 = 'value1'`)
    expect(result).toContain(`const line50 = 'value50'`)
  })

  test('full mode sends all lines with no omission markers', () => {
    const file = createMockFile(300)
    const findings = [createMockFinding(150)]
    const context = createMockProjectContext()

    const result = buildHighContextValidationRequest(file, findings, context, {
      contextMode: 'full',
    })

    expect(result).not.toContain('lines omitted')
    expect(result).not.toContain('Showing')
    expect(result).toContain(`const line1 = 'value1'`)
    expect(result).toContain(`const line300 = 'value300'`)
  })

  test('default mode uses scoped (large file gets omission markers)', () => {
    const file = createMockFile(300)
    const findings = [createMockFinding(150)]
    const context = createMockProjectContext()

    // No options passed = default
    const result = buildHighContextValidationRequest(file, findings, context)

    expect(result).toContain('lines omitted')
  })
})

// ============================================================================
// buildMultiFileValidationRequest
// ============================================================================

describe('buildMultiFileValidationRequest', () => {
  test('multi-file scoped mode extracts per-file', () => {
    const file1 = createMockFile(300, 'src/auth.ts')
    const file2 = createMockFile(50, 'src/utils.ts')
    const findings1 = [createMockFinding(150)]
    const findings2 = [createMockFinding(25)]
    const context = createMockProjectContext()

    const result = buildMultiFileValidationRequest(
      [
        { file: file1, findings: findings1 },
        { file: file2, findings: findings2 },
      ],
      context,
      { contextMode: 'scoped' }
    )

    // File 1 (large) should have omission
    expect(result).toContain('FILE 1: src/auth.ts')
    expect(result).toContain('lines omitted')
    // File 2 (small) should be full
    expect(result).toContain('FILE 2: src/utils.ts')
    // Project context appears once
    const contextMatches = result.split('## Project Context').length - 1
    expect(contextMatches).toBe(1)
  })

  test('multi-file full mode sends all content', () => {
    const file1 = createMockFile(300, 'src/auth.ts')
    const findings1 = [createMockFinding(150)]
    const context = createMockProjectContext()

    const result = buildMultiFileValidationRequest(
      [{ file: file1, findings: findings1 }],
      context,
      { contextMode: 'full' }
    )

    expect(result).not.toContain('lines omitted')
    expect(result).toContain(`const line300 = 'value300'`)
  })
})

// ============================================================================
// Taint Annotation Tests
// ============================================================================

function createMockTaintAnalysis(filePath: string): FileTaintAnalysis {
  return {
    filePath,
    sources: [
      {
        line: 12,
        expression: 'req.params.id',
        variable: 'id',
        sourceType: 'http_params',
        confidence: 'high',
      },
    ],
    taintedVariables: [
      {
        name: 'userId',
        taintedAt: 18,
        source: {
          line: 12,
          expression: 'req.params.id',
          variable: 'id',
          sourceType: 'http_params',
          confidence: 'high',
        },
        propagation: 'direct_assignment',
        sanitised: false,
        scopeDepth: 0,
      },
    ],
    taintPaths: [
      {
        source: {
          line: 12,
          expression: 'req.params.id',
          variable: 'id',
          sourceType: 'http_params',
          confidence: 'high',
        },
        chain: [
          {
            name: 'userId',
            taintedAt: 18,
            source: {
              line: 12,
              expression: 'req.params.id',
              variable: 'id',
              sourceType: 'http_params',
              confidence: 'high',
            },
            propagation: 'direct_assignment',
            sanitised: false,
            scopeDepth: 0,
          },
        ],
        sink: {
          line: 42,
          expression: 'db.query("SELECT * FROM users WHERE id = " + userId)',
          sinkType: 'sql_query',
        },
        sanitised: false,
        confidence: 'high',
      },
    ],
    sanitisers: [],
  }
}

describe('buildFileTaintSummary', () => {
  test('returns summary for file with taint data', () => {
    const analyses = new Map<string, FileTaintAnalysis>()
    analyses.set('src/test.ts', createMockTaintAnalysis('src/test.ts'))

    const result = buildFileTaintSummary('src/test.ts', analyses)

    expect(result).toContain('Data Flow Analysis')
    expect(result).toContain('http_params')
    expect(result).toContain('Taint paths: 1')
    expect(result).toContain('1 unsanitised')
  })

  test('returns empty string for file without taint data', () => {
    const analyses = new Map<string, FileTaintAnalysis>()

    const result = buildFileTaintSummary('src/other.ts', analyses)

    expect(result).toBe('')
  })
})

describe('buildFindingTaintAnnotation', () => {
  test('shows taint path when sink matches finding line', () => {
    const analyses = new Map<string, FileTaintAnalysis>()
    analyses.set('src/test.ts', createMockTaintAnalysis('src/test.ts'))
    const finding = createMockFinding(42, 'sql_injection' as any)

    const result = buildFindingTaintAnnotation(finding, 'src/test.ts', analyses)

    expect(result).toContain('User input reaches this sink')
    expect(result).toContain('req.params.id')
    expect(result).toContain('http_params')
    expect(result).toContain('sql_query')
    expect(result).toContain('Sanitised: No')
    expect(result).toContain('Confidence: high')
  })

  test('shows "no data flow" when file has sources but no matching path', () => {
    const analyses = new Map<string, FileTaintAnalysis>()
    analyses.set('src/test.ts', createMockTaintAnalysis('src/test.ts'))
    const finding = createMockFinding(99) // line 99 has no matching sink

    const result = buildFindingTaintAnnotation(finding, 'src/test.ts', analyses)

    expect(result).toContain('No user-input data flow reaches this line')
  })

  test('returns empty string when file has no taint analysis', () => {
    const analyses = new Map<string, FileTaintAnalysis>()
    const finding = createMockFinding(42)

    const result = buildFindingTaintAnnotation(finding, 'src/test.ts', analyses)

    expect(result).toBe('')
  })
})

describe('buildMultiFileValidationRequest with taint data', () => {
  test('includes taint annotations when taint data provided', () => {
    const file = createMockFile(50, 'src/test.ts')
    const finding = createMockFinding(42, 'sql_injection' as any)
    const context = createMockProjectContext()

    const analyses = new Map<string, FileTaintAnalysis>()
    analyses.set('src/test.ts', createMockTaintAnalysis('src/test.ts'))

    const result = buildMultiFileValidationRequest(
      [{ file, findings: [finding] }],
      context,
      { contextMode: 'scoped' },
      analyses
    )

    expect(result).toContain('Data Flow Analysis')
    expect(result).toContain('User input reaches this sink')
  })

  test('no taint annotations when no taint data provided', () => {
    const file = createMockFile(50, 'src/test.ts')
    const finding = createMockFinding(42)
    const context = createMockProjectContext()

    const result = buildMultiFileValidationRequest(
      [{ file, findings: [finding] }],
      context,
      { contextMode: 'scoped' }
    )

    expect(result).not.toContain('Data Flow Analysis')
    expect(result).not.toContain('Taint Analysis')
  })
})
