/**
 * Middleware Auth Detection
 * Detects global authentication middleware (Next.js, Express, etc.)
 * and determines which routes are protected
 */

import type { ScanFile } from '../shared/types'
import { extractAllImports, resolveImportPath, buildFileIndex } from './import-resolver'

/**
 * Configuration describing detected auth middleware
 */
export interface MiddlewareAuthConfig {
  /** Whether auth middleware was detected */
  hasAuthMiddleware: boolean
  /** The type of auth detected */
  authType?: 'clerk' | 'nextauth' | 'auth0' | 'supabase' | 'custom' | 'unknown'
  /** File path where middleware was found */
  middlewareFile?: string
  /** Protected path patterns (glob-like) */
  protectedPaths: string[]
  /** Public/excluded path patterns */
  publicPaths: string[]
  /** Raw matcher patterns found */
  matchers: string[]
  /** Additional context about the auth setup */
  context: {
    usesAuthProtect: boolean
    usesGetToken: boolean
    usesSession: boolean
    hasPublicRoutes: boolean
  }
}

/**
 * Detect global auth middleware from scanned files
 * Looks for Next.js middleware.ts, Express auth patterns, etc.
 */
export function detectGlobalAuthMiddleware(files: ScanFile[]): MiddlewareAuthConfig {
  const result: MiddlewareAuthConfig = {
    hasAuthMiddleware: false,
    protectedPaths: [],
    publicPaths: [],
    matchers: [],
    context: {
      usesAuthProtect: false,
      usesGetToken: false,
      usesSession: false,
      hasPublicRoutes: false,
    },
  }

  // Find middleware files
  const middlewareFile = files.find(f =>
    /^(src\/)?middleware\.(ts|js)$/.test(f.path) ||
    f.path.endsWith('/middleware.ts') ||
    f.path.endsWith('/middleware.js')
  )

  if (!middlewareFile) {
    return result
  }

  result.middlewareFile = middlewareFile.path
  const content = middlewareFile.content

  // Resolve imported middleware helpers (single-hop follow)
  const resolvedContent = resolveMiddlewareImports(content, middlewareFile.path, files)
  const combinedContent = content + '\n' + resolvedContent

  // Detect auth provider type (check both root middleware and imported helpers)
  result.authType = detectAuthType(combinedContent)

  if (result.authType) {
    result.hasAuthMiddleware = true
  }

  // Extract protected path matchers from root middleware
  const { protectedPaths, publicPaths, matchers } = extractPathMatchers(content)
  result.protectedPaths = protectedPaths
  result.publicPaths = publicPaths
  result.matchers = matchers

  // Extract route protection patterns from imported helper files
  if (resolvedContent) {
    const helperPaths = extractRouteProtectionPaths(resolvedContent)
    for (const p of helperPaths.protectedPaths) {
      if (!result.protectedPaths.includes(p)) result.protectedPaths.push(p)
    }
    for (const p of helperPaths.publicPaths) {
      if (!result.publicPaths.includes(p)) result.publicPaths.push(p)
    }
  }

  // Detect specific auth patterns (check both root and helpers)
  result.context.usesAuthProtect = /auth\.protect\(\)|auth\(\)\.protect\(\)|requireAuth\(\)/i.test(combinedContent)
  result.context.usesGetToken = /getToken\(|getAuth\(/i.test(combinedContent)
  result.context.usesSession = /getSession\(|getServerSession\(|getUser\(/i.test(combinedContent)
  result.context.hasPublicRoutes = /isPublicRoute|publicRoutes|ignoredRoutes|excludedPaths|isAuthRoute/i.test(combinedContent)

  // If we found auth imports but no explicit matchers, assume /api/** is protected
  if (result.hasAuthMiddleware && result.protectedPaths.length === 0) {
    // Check if middleware runs on all routes or has default API protection
    if (!result.context.hasPublicRoutes || /\/api/i.test(combinedContent)) {
      result.protectedPaths.push('/api/**')
    }
  }

  return result
}

/**
 * Resolve local imports from middleware.ts to find helper files.
 * Single-hop follow: reads the content of imported modules so we can
 * detect auth patterns and route protection logic defined there.
 */
function resolveMiddlewareImports(
  middlewareContent: string,
  middlewarePath: string,
  files: ScanFile[]
): string {
  const fileIndex = buildFileIndex(files)
  const imports = extractAllImports(middlewareContent, middlewarePath)
  const importedContent: string[] = []

  for (const imp of imports) {
    if (!imp.isLocal) continue
    const resolved = resolveImportPath(imp.importPath, middlewarePath, fileIndex)
    if (resolved) {
      importedContent.push(resolved.content)
    }
  }

  return importedContent.join('\n')
}

/**
 * Extract route protection paths from middleware helper content.
 * Detects patterns like:
 *   pathname.startsWith('/dashboard')
 *   isProtectedRoute = pathname.startsWith('/scan')
 *   isPublicRoute / isAuthRoute patterns
 */
function extractRouteProtectionPaths(content: string): {
  protectedPaths: string[]
  publicPaths: string[]
} {
  const protectedPaths: string[] = []
  const publicPaths: string[] = []

  // Detect: const isProtectedRoute = pathname.startsWith('/dashboard') || pathname.startsWith('/scan')
  // We look for variables named isProtected* and extract their startsWith paths
  const protectedVarPattern = /is(?:Protected|Authed|Private)(?:Route|Path)?\s*=\s*([^\n]+)/gi
  let match
  while ((match = protectedVarPattern.exec(content)) !== null) {
    const expr = match[1]
    const startsWithMatches = expr.matchAll(/startsWith\s*\(\s*['"]([^'"]+)['"]\s*\)/g)
    for (const sw of startsWithMatches) {
      const path = sw[1]
      if (!protectedPaths.includes(path)) {
        protectedPaths.push(path)
      }
    }
  }

  // Detect: const isPublicRoute = ... / const isAuthRoute = ...
  const publicVarPattern = /is(?:Public|Auth|Open|Unauthenticated)(?:Route|Path)?\s*=\s*([^\n]+)/gi
  while ((match = publicVarPattern.exec(content)) !== null) {
    const expr = match[1]
    const startsWithMatches = expr.matchAll(/startsWith\s*\(\s*['"]([^'"]+)['"]\s*\)/g)
    for (const sw of startsWithMatches) {
      const path = sw[1]
      if (!publicPaths.includes(path)) {
        publicPaths.push(path)
      }
    }
    // Also match: pathname === '/login'
    const equalsMatches = expr.matchAll(/===?\s*['"]([^'"]+)['"]/g)
    for (const eq of equalsMatches) {
      const path = eq[1]
      if (path.startsWith('/') && !publicPaths.includes(path)) {
        publicPaths.push(path)
      }
    }
  }

  // Detect redirect for unauthenticated users: if (!user && isProtectedRoute) { redirect }
  // This confirms the middleware actually enforces auth, but the paths are already extracted above

  // Detect standalone pathname.startsWith in conditional blocks (e.g., if statements)
  // Only if not already captured above
  const standalonePattern = /!user\s*&&\s*.*?pathname\.startsWith\s*\(\s*['"]([^'"]+)['"]/g
  while ((match = standalonePattern.exec(content)) !== null) {
    const path = match[1]
    if (!protectedPaths.includes(path)) {
      protectedPaths.push(path)
    }
  }

  return { protectedPaths, publicPaths }
}

/**
 * Detect which auth provider/library is being used
 */
function detectAuthType(content: string): MiddlewareAuthConfig['authType'] {
  // Clerk patterns
  if (/clerkMiddleware|@clerk\/nextjs|clerkClient|auth\(\)\.protect/i.test(content)) {
    return 'clerk'
  }

  // NextAuth patterns
  if (/next-auth|NextAuth|getServerSession|withAuth\(/i.test(content)) {
    return 'nextauth'
  }

  // Auth0 patterns
  if (/@auth0\/nextjs|withPageAuthRequired|withApiAuthRequired/i.test(content)) {
    return 'auth0'
  }

  // Supabase Auth patterns
  if (/@supabase\/ssr|supabase\/middleware|createServerClient.*supabase|updateSession/i.test(content)) {
    return 'supabase'
  }

  // Generic auth middleware patterns
  if (/authMiddleware|requireAuth|verifyToken|checkAuth|isAuthenticated/i.test(content)) {
    return 'custom'
  }

  // Check for common auth imports/usage that suggest middleware is doing auth
  if (/authorization|bearer|jwt|session\.user|getToken/i.test(content)) {
    return 'unknown'
  }

  return undefined
}

/**
 * Extract path matchers from middleware config
 */
function extractPathMatchers(content: string): {
  protectedPaths: string[]
  publicPaths: string[]
  matchers: string[]
} {
  const protectedPaths: string[] = []
  const publicPaths: string[] = []
  const matchers: string[] = []

  // Next.js config.matcher pattern
  // export const config = { matcher: [...] }
  const matcherArrayMatch = content.match(/config\s*=\s*\{[^}]*matcher\s*:\s*\[([\s\S]*?)\]/m)
  if (matcherArrayMatch) {
    const matcherContent = matcherArrayMatch[1]
    // Extract string patterns from the array
    const patterns = matcherContent.match(/['"`]([^'"`]+)['"`]/g) || []
    for (const p of patterns) {
      const path = p.replace(/['"`]/g, '')
      matchers.push(path)
      // Classify as protected or public based on common patterns
      if (path.includes('/api') || path.includes('/(protected)') || path.includes('/dashboard')) {
        protectedPaths.push(path)
      }
    }
  }

  // Single matcher pattern
  // export const config = { matcher: '/api/:path*' }
  const singleMatcherMatch = content.match(/config\s*=\s*\{[^}]*matcher\s*:\s*['"`]([^'"`]+)['"`]/m)
  if (singleMatcherMatch) {
    const path = singleMatcherMatch[1]
    matchers.push(path)
    if (path.includes('/api')) {
      protectedPaths.push(path)
    }
  }

  // Clerk-specific: createRouteMatcher for public routes
  // const isPublicRoute = createRouteMatcher(['/sign-in', '/sign-up', ...])
  const publicRouteMatch = content.match(/createRouteMatcher\s*\(\s*\[([\s\S]*?)\]/m)
  if (publicRouteMatch) {
    const routeContent = publicRouteMatch[1]
    const patterns = routeContent.match(/['"`]([^'"`]+)['"`]/g) || []
    for (const p of patterns) {
      const path = p.replace(/['"`]/g, '')
      publicPaths.push(path)
    }
  }

  // Look for publicRoutes/ignoredRoutes arrays
  const publicRoutesMatch = content.match(/(publicRoutes|ignoredRoutes|excludedPaths)\s*[=:]\s*\[([\s\S]*?)\]/m)
  if (publicRoutesMatch) {
    const routeContent = publicRoutesMatch[2]
    const patterns = routeContent.match(/['"`]([^'"`]+)['"`]/g) || []
    for (const p of patterns) {
      const path = p.replace(/['"`]/g, '')
      publicPaths.push(path)
    }
  }

  // If we found public paths but no explicit protected paths,
  // assume everything else under /api is protected
  if (publicPaths.length > 0 && protectedPaths.length === 0) {
    protectedPaths.push('/api/**')
  }

  return { protectedPaths, publicPaths, matchers }
}

/**
 * Check if a given route path is protected by the middleware
 */
export function isRouteProtectedByMiddleware(
  routePath: string,
  config: MiddlewareAuthConfig
): { isProtected: boolean; reason: string } {
  if (!config.hasAuthMiddleware) {
    return { isProtected: false, reason: 'No auth middleware detected' }
  }

  // Normalize path
  const normalizedPath = routePath.startsWith('/') ? routePath : `/${routePath}`

  // Check if explicitly public
  for (const publicPath of config.publicPaths) {
    if (matchPath(normalizedPath, publicPath)) {
      return { isProtected: false, reason: `Matches public route pattern: ${publicPath}` }
    }
  }

  // Check if explicitly protected
  for (const protectedPath of config.protectedPaths) {
    if (matchPath(normalizedPath, protectedPath)) {
      return {
        isProtected: true,
        reason: `Protected by ${config.authType || 'auth'} middleware (matches: ${protectedPath})`,
      }
    }
  }

  // Check matchers — only use as a protection signal when we have NO explicit
  // protectedPaths from helper analysis.  config.matchers defines which routes
  // the middleware *runs on*, not which routes are *protected*.  When explicit
  // protectedPaths exist the middleware decides protection internally, and the
  // matcher is merely a run-scope (e.g. Next.js "match everything except static").
  if (config.protectedPaths.length === 0) {
    for (const matcher of config.matchers) {
      if (matchPath(normalizedPath, matcher)) {
        return {
          isProtected: true,
          reason: `Protected by middleware matcher: ${matcher}`,
        }
      }
    }
  }

  // Default: if we have auth middleware and no explicit public match,
  // consider API routes as protected
  if (normalizedPath.startsWith('/api/')) {
    return {
      isProtected: true,
      reason: `API route likely protected by ${config.authType || 'auth'} middleware`,
    }
  }

  return { isProtected: false, reason: 'Route not covered by middleware matchers' }
}

/**
 * Extract route path from a file path (e.g., app/api/users/route.ts -> /api/users)
 */
export function getRoutePathFromFile(filePath: string): string | null {
  // Next.js App Router: app/api/users/route.ts -> /api/users
  const appRouterMatch = filePath.match(/app(\/.*?)\/route\.(ts|js)$/i)
  if (appRouterMatch) {
    return appRouterMatch[1]
  }

  // Next.js Pages Router: pages/api/users.ts -> /api/users
  const pagesRouterMatch = filePath.match(/pages(\/api\/.*?)\.(ts|js)$/i)
  if (pagesRouterMatch) {
    return pagesRouterMatch[1]
  }

  // Express-style routes: routes/users.ts (harder to determine path)
  // Return null for now - would need more context
  return null
}

/**
 * Simple glob-like path matching
 * Supports: ** (any path), * (single segment), :param (path params)
 */
function matchPath(actualPath: string, pattern: string): boolean {
  // Normalize
  const normActual = actualPath.replace(/\/+/g, '/')
  let normPattern = pattern.replace(/\/+/g, '/')

  // Negative lookahead matchers (e.g., Next.js /((?!_next/static|...).*)) are
  // catch-all patterns excluding static assets — treat as "match all routes"
  if (normPattern.includes('(?!')) {
    return true
  }
  // Other regex constructs ((?:), (?=)) — attempt to evaluate as actual regex
  if (normPattern.includes('(?:') || normPattern.includes('(?=')) {
    try {
      return new RegExp(normPattern).test(actualPath)
    } catch {
      return false  // Unparseable pattern — don't assume match
    }
  }

  // Clerk-style regex patterns: /sign-in(.*), /api/webhook(.*)
  // Convert (.*) → **, (.+) → **, and strip other regex groups
  normPattern = normPattern
    .replace(/\(\.\*\)/g, '**')
    .replace(/\(\.\+\)/g, '**')
    .replace(/\(([^)]+)\)/g, '$1')   // unwrap remaining groups

  // Handle Next.js :path* patterns
  normPattern = normPattern
    .replace(/:path\*/g, '**')
    .replace(/:\w+/g, '[^/]+')

  // Convert glob to regex
  const regexPattern = normPattern
    .replace(/\*\*/g, '<<<DOUBLESTAR>>>')
    .replace(/\*/g, '[^/]*')
    .replace(/<<<DOUBLESTAR>>>/g, '.*')
    .replace(/\//g, '\\/')

  const regex = new RegExp(`^${regexPattern}$`)
  return regex.test(normActual)
}

/**
 * Get user-scoping patterns from file content
 * Detects patterns like: user_id, userId, session.user.id
 */
export function detectUserScopingPatterns(content: string): {
  hasUserScoping: boolean
  patterns: string[]
} {
  const patterns: string[] = []

  // Common user ID patterns in queries/operations
  const userIdPatterns = [
    /\.eq\s*\(\s*['"`]user_id['"`]/i,        // Supabase .eq('user_id', ...)
    /\.filter\s*\(\s*user_id\s*=/i,           // Django-style
    /where\s*\(\s*['"`]?user_id['"`]?\s*=/i,  // SQL-like
    /userId\s*[=:]/i,                          // Generic userId
    /user\.id\s*[=:]/i,                        // user.id
    /session\.user\.id/i,                      // Next.js session
    /req\.user\.id/i,                          // Express req.user
    /getCurrentUserId\s*\(/i,                  // Custom helper
    /getAuthUser\s*\(/i,                       // Auth helper
    /auth\(\).*\.userId/i,                     // Clerk auth().userId
  ]

  for (const pattern of userIdPatterns) {
    if (pattern.test(content)) {
      patterns.push(pattern.source)
    }
  }

  return {
    hasUserScoping: patterns.length > 0,
    patterns,
  }
}
