/**
 * Project Model Builder — Assembles the full project model from source files.
 *
 * Orchestrates middleware detection, auth imports, project context, and
 * detector context into a single ProjectModel used by the scan pipeline.
 */

import type { ScanFile, DetectorContext } from '../shared/types'
import { createEmptyDetectorContext, SCANNABLE_EXTENSIONS } from '../shared/types'
import type { ProjectContext } from './project-context'
import { buildProjectContext } from './project-context'
import { detectGlobalAuthMiddleware, type MiddlewareAuthConfig } from './middleware-detector'
import { buildFileAuthImports, type FileAuthImports } from './imported-auth-detector'
import {
  isServerOnlyFile,
  isClientBundledFile,
  isTestOrMockFile,
  isToolingDirectory,
} from '../parse/file-classifier'
import type { FileTaintAnalysis, ContextEngineResult } from './taint-types'
import { discoverSources } from './source-discovery'
import { detectSanitisers } from './sanitiser-detection'
import { propagateTaint } from './taint-tracker'
import { matchSinks } from './sink-matcher'
import { discoverRoutes } from './route-discovery'
import type { RouteMap } from './route-discovery'
import { resolveRouteAuth } from './route-auth-resolver'
import { buildModuleGraph } from './module-graph'
import type { ModuleGraphStats } from './module-graph'
import { buildFunctionProfiles } from './function-classifier'
import { analyzeCrossFileTaint } from './cross-file-taint'
import { analyzeFrameworkPatterns } from './framework-models'

// ============================================================================
// Types
// ============================================================================

export interface RouteDiscoveryStats {
  totalRoutes: number
  authedRoutes: number
  publicRoutes: number
  unprotectedRoutes: number
  frameworksDetected: string[]
  duration: number
}

export interface ProjectModel {
  contextEngine: ContextEngineResult
  middlewareConfig: MiddlewareAuthConfig
  fileAuthImports: Map<string, FileAuthImports>
  detectorContext: DetectorContext
  taintStats: TaintAnalysisStats
  routeStats: RouteDiscoveryStats
  moduleGraphStats?: ModuleGraphStats
}

// ============================================================================
// Project Model Builder
// ============================================================================

/**
 * Build the full project model from source files.
 * Detects auth middleware, file auth imports, project context, and detector context.
 */
export function buildProjectModel(files: ScanFile[]): ProjectModel {
  const middlewareConfig = detectGlobalAuthMiddleware(files)
  const fileAuthImports = buildFileAuthImports(files)
  const projectContext = buildProjectContext(files)
  const detectorContext = buildDetectorContext(projectContext, middlewareConfig)
  const { analyses: fileTaintAnalyses, stats: taintStats } = buildFileTaintAnalyses(files)

  // CE Phase 4: Module Graph & Cross-File Analysis
  const moduleGraph = buildModuleGraph(files)
  const functionProfiles = buildFunctionProfiles(files, moduleGraph)
  const crossFileTaintPaths = analyzeCrossFileTaint(
    files, moduleGraph, functionProfiles, fileTaintAnalyses
  )

  // CE Phase 2: Route Discovery (uses module graph for cross-file auth)
  const routeStart = Date.now()
  const { routeMap: rawRouteMap, mountPoints } = discoverRoutes(files, projectContext.frameworks.primary)
  const routeMap = resolveRouteAuth(rawRouteMap, mountPoints, files, middlewareConfig, moduleGraph)
  const routeDuration = Date.now() - routeStart

  const routeStats = buildRouteStats(routeMap, routeDuration)

  // CE Phase 5: Framework-Aware Analysis
  const frameworkAnalyses = analyzeFrameworkPatterns(files, projectContext)

  const contextEngine: ContextEngineResult = {
    projectContext,
    fileTaintAnalyses,
    routeMap,
    moduleGraph,
    crossFileTaintPaths,
    functionProfiles,
    frameworkAnalyses,
  }

  return {
    contextEngine,
    middlewareConfig,
    fileAuthImports,
    detectorContext,
    taintStats,
    routeStats,
    moduleGraphStats: moduleGraph.stats,
  }
}

// ============================================================================
// Route Discovery Stats (Context Engine Phase 2)
// ============================================================================

function buildRouteStats(routeMap: RouteMap, duration: number): RouteDiscoveryStats {
  const routes = routeMap.routes
  const authedRoutes = routes.filter(r => r.authMiddleware.length > 0).length
  const publicRoutes = routes.filter(r => r.isPublic).length
  const unprotectedRoutes = routes.filter(r => r.authMiddleware.length === 0 && !r.isPublic).length
  const frameworksDetected = [...new Set(routes.map(r => r.framework))]

  return {
    totalRoutes: routes.length,
    authedRoutes,
    publicRoutes,
    unprotectedRoutes,
    frameworksDetected,
    duration,
  }
}

// ============================================================================
// Taint Analysis (Context Engine Phase 1)
// ============================================================================

const CODE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.php', '.go', '.java', '.cs'])

function isCodeFile(filePath: string): boolean {
  const ext = filePath.substring(filePath.lastIndexOf('.'))
  return CODE_EXTENSIONS.has(ext.toLowerCase())
}

function analyzeFileTaint(content: string, filePath: string): FileTaintAnalysis {
  const sources = discoverSources(content, filePath)
  if (sources.length === 0) {
    return { filePath, sources: [], taintedVariables: [], taintPaths: [], sanitisers: [] }
  }
  const sanitisers = detectSanitisers(content, filePath)
  const taintedVariables = propagateTaint(content, filePath, sources, sanitisers)
  const taintPaths = matchSinks(content, filePath, taintedVariables, sanitisers)
  return { filePath, sources, taintedVariables, taintPaths, sanitisers }
}

export interface TaintAnalysisStats {
  filesAnalyzed: number
  filesWithSources: number
  filesWithTaintPaths: number
  totalSources: number
  totalTaintedVars: number
  totalTaintPaths: number
  unsanitisedPaths: number
  sanitisedPaths: number
  totalSanitisers: number
  sourceTypeBreakdown: Record<string, number>
  sinkTypeBreakdown: Record<string, number>
  duration: number
}

function buildFileTaintAnalyses(files: ScanFile[]): { analyses: Map<string, FileTaintAnalysis>; stats: TaintAnalysisStats } {
  const startTime = Date.now()
  const analyses = new Map<string, FileTaintAnalysis>()
  const sourceTypeBreakdown: Record<string, number> = {}
  const sinkTypeBreakdown: Record<string, number> = {}
  let filesAnalyzed = 0
  let totalSources = 0
  let totalTaintedVars = 0
  let totalTaintPaths = 0
  let unsanitisedPaths = 0
  let sanitisedPaths = 0
  let totalSanitisers = 0

  for (const file of files) {
    if (!isCodeFile(file.path) || isTestOrMockFile(file.path)) continue
    filesAnalyzed++
    const analysis = analyzeFileTaint(file.content, file.path)
    if (analysis.sources.length > 0 || analysis.taintPaths.length > 0) {
      analyses.set(file.path, analysis)
    }

    // Accumulate stats
    totalSources += analysis.sources.length
    totalTaintedVars += analysis.taintedVariables.length
    totalTaintPaths += analysis.taintPaths.length
    totalSanitisers += analysis.sanitisers.length

    for (const src of analysis.sources) {
      sourceTypeBreakdown[src.sourceType] = (sourceTypeBreakdown[src.sourceType] || 0) + 1
    }
    for (const tp of analysis.taintPaths) {
      if (tp.sanitised) sanitisedPaths++
      else unsanitisedPaths++
      sinkTypeBreakdown[tp.sink.sinkType] = (sinkTypeBreakdown[tp.sink.sinkType] || 0) + 1
    }
  }

  return {
    analyses,
    stats: {
      filesAnalyzed,
      filesWithSources: Array.from(analyses.values()).filter(a => a.sources.length > 0).length,
      filesWithTaintPaths: Array.from(analyses.values()).filter(a => a.taintPaths.length > 0).length,
      totalSources,
      totalTaintedVars,
      totalTaintPaths,
      unsanitisedPaths,
      sanitisedPaths,
      totalSanitisers,
      sourceTypeBreakdown,
      sinkTypeBreakdown,
      duration: Date.now() - startTime,
    },
  }
}

// ============================================================================
// Detector Context Builder (Phase 2b)
// ============================================================================

/**
 * Build a DetectorContext from ProjectContext
 * This provides rich context to Layer 2 detectors for smarter decisions
 */
export function buildDetectorContext(
  projectContext: ProjectContext,
  middlewareConfig: MiddlewareAuthConfig | undefined
): DetectorContext {
  // Map ProjectContext to DetectorContext format
  const context = createEmptyDetectorContext()

  // Auth context
  if (middlewareConfig?.hasAuthMiddleware) {
    context.auth.middleware = {
      hasAuthMiddleware: true,
      authType: middlewareConfig.authType ?? null,
      protectedPaths: middlewareConfig.protectedPaths,
      publicPaths: middlewareConfig.publicPaths,
    }
  }

  // Auth helpers from project context
  if (projectContext.auth.throwingAuthHelpers.length > 0) {
    context.auth.authHelpers = {
      hasThrowingHelpers: true,
      throwingHelperNames: projectContext.auth.throwingAuthHelpers.map(h => h.name),
    }
  }

  // Public endpoints
  context.auth.publicEndpoints = projectContext.auth.publicPaths

  // Framework context
  context.framework = {
    primary: projectContext.frameworks.primary ?? null,
    frontend: projectContext.frameworks.frontend ?? null,
    hasTypeScript: projectContext.frameworks.usesTypeScript,
  }

  // Data access context
  context.dataAccess = {
    orm: mapOrmToDataAccessOrm(projectContext.dataAccess.orm),
    hasRLS: projectContext.dataAccess.hasRLS,
    validationLib: mapValidationLib(projectContext.dataAccess.validationLibrary),
  }

  return context
}

/**
 * Build file-specific context for a single file
 * This is merged with the project-level DetectorContext
 */
export function buildFileDetectorContext(
  baseContext: DetectorContext,
  filePath: string
): DetectorContext {
  return {
    ...baseContext,
    file: {
      isServerOnly: isServerOnlyFile(filePath),
      isClientBundled: isClientBundledFile(filePath),
      isTestFile: isTestOrMockFile(filePath),
      isConfigFile: isConfigFile(filePath),
      isToolingDir: isToolingDirectory(filePath),
    },
  }
}

/**
 * Check if file is a configuration file
 */
export function isConfigFile(filePath: string): boolean {
  const configPatterns = [
    /config\.(ts|js|json|yaml|yml)$/i,
    /settings\.(ts|js|json)$/i,
    /constants\.(ts|js)$/i,
    /\.config\.(ts|js|mjs|cjs)$/i,
    /\.env/i,
    /tsconfig\.json$/i,
    /package\.json$/i,
    /jest\.config/i,
    /vitest\.config/i,
    /eslint/i,
    /prettier/i,
  ]
  return configPatterns.some(p => p.test(filePath))
}

/**
 * Map ProjectContext ORM to DetectorContext ORM
 */
function mapOrmToDataAccessOrm(
  orm: ProjectContext['dataAccess']['orm']
): DetectorContext['dataAccess']['orm'] {
  if (!orm) return null
  switch (orm) {
    case 'prisma':
    case 'drizzle':
    case 'typeorm':
    case 'sequelize':
    case 'knex':
      return orm
    default:
      return null
  }
}

/**
 * Map validation library to DetectorContext format
 */
function mapValidationLib(
  lib: string | undefined
): DetectorContext['dataAccess']['validationLib'] {
  if (!lib) return null
  switch (lib) {
    case 'zod':
    case 'yup':
    case 'joi':
    case 'valibot':
      return lib
    default:
      return null
  }
}
