import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { dirname, join, relative } from 'node:path'
import { fileURLToPath } from 'node:url'

import type { AccessKeyInput } from '../access-keys'

export interface AccessKeyUsage {
  file: string
  key: AccessKeyInput
}

export interface AccessKeySymbolUsage {
  file: string
  symbol: string
}

const repoRoot = findRepoRoot(dirname(fileURLToPath(import.meta.url)))

const RUNTIME_SOURCE_ROOTS = [
  join(repoRoot, 'apps', 'command-center', 'src'),
  join(repoRoot, 'packages', 'ui', 'src'),
  join(repoRoot, 'apps', 'api', 'src'),
  join(repoRoot, 'external', '_template', 'ui', 'src')
]

const STRING_LITERAL_PATTERNS = [
  /<AccessGuard\b[\s\S]*?\baccessKey\s*=\s*["']([^"']+)["']/g,
  /<AccessGuard\b[\s\S]*?\baccessKey\s*=\s*\{\s*["']([^"']+)["']\s*\}/g,
  /\buseAccess\(\s*["']([^"']+)["']\s*\)/g,
  /\brequireAccess\(\s*["']([^"']+)["']\s*[,)]/g
]

const ACCESS_KEYS_PATTERN = /\bAccessKeys\.([A-Za-z_$][A-Za-z0-9_$]*)\b/g

function findRepoRoot(startDirectory: string): string {
  let current = startDirectory

  while (current !== dirname(current)) {
    if (existsSync(join(current, 'pnpm-workspace.yaml'))) return current
    current = dirname(current)
  }

  throw new Error('Could not find monorepo root from access-key scan test')
}

function isRuntimeSourceFile(file: string): boolean {
  if (!/\.(ts|tsx)$/.test(file)) return false
  if (file.includes(`${join('', '__tests__').slice(1)}`)) return false
  if (file.includes('__tests__')) return false
  if (/\.(test|spec)\.(ts|tsx)$/.test(file)) return false
  return !file.endsWith('routeTree.gen.ts')
}

function listRuntimeSourceFiles(directory: string): string[] {
  if (!existsSync(directory)) return []

  const entries = readdirSync(directory, { withFileTypes: true })
  const files: string[] = []

  for (const entry of entries) {
    const entryPath = join(directory, entry.name)

    if (entry.isDirectory()) {
      if (['node_modules', 'dist', '.next', 'coverage'].includes(entry.name)) continue
      files.push(...listRuntimeSourceFiles(entryPath))
      continue
    }

    if (isRuntimeSourceFile(entryPath)) files.push(entryPath)
  }

  return files
}

function relativePath(file: string): string {
  return relative(repoRoot, file).replaceAll('\\', '/')
}

function collectMatches<T>(
  content: string,
  pattern: RegExp,
  build: (match: RegExpExecArray) => T
): T[] {
  const matches: T[] = []
  pattern.lastIndex = 0

  for (let match = pattern.exec(content); match !== null; match = pattern.exec(content)) {
    matches.push(build(match))
  }

  return matches
}

export function scanAccessKeyUsages(): {
  stringLiterals: AccessKeyUsage[]
  accessKeySymbols: AccessKeySymbolUsage[]
} {
  const stringLiterals: AccessKeyUsage[] = []
  const accessKeySymbols: AccessKeySymbolUsage[] = []

  for (const sourceRoot of RUNTIME_SOURCE_ROOTS) {
    for (const file of listRuntimeSourceFiles(sourceRoot)) {
      const content = readFileSync(file, 'utf8')
      const path = relativePath(file)

      for (const pattern of STRING_LITERAL_PATTERNS) {
        stringLiterals.push(...collectMatches(content, pattern, (match) => ({ file: path, key: match[1] })))
      }

      accessKeySymbols.push(
        ...collectMatches(content, ACCESS_KEYS_PATTERN, (match) => ({ file: path, symbol: match[1] }))
      )
    }
  }

  return { stringLiterals, accessKeySymbols }
}
