/**
 * cli:eslint-validator — execute.ts
 * 5 regles d'analyse statique par regex sur fichiers .tsx/.ts
 * Ne valide rien — recoit des donnees deja validees
 */
import { readdirSync, readFileSync, statSync } from 'node:fs'
import { join, relative, basename, sep, posix } from 'node:path'
import type { ESLintInput, CheckResult, ESLintReport } from './types'

// ─── Helpers ───

/** Recursively collect files matching extensions */
function collectFiles(dir: string, extensions: string[]): string[] {
  const results: string[] = []
  let entries: string[]
  try {
    entries = readdirSync(dir)
  } catch {
    return results
  }
  for (const entry of entries) {
    const fullPath = join(dir, entry)
    let stat
    try {
      stat = statSync(fullPath)
    } catch {
      continue
    }
    if (stat.isDirectory()) {
      results.push(...collectFiles(fullPath, extensions))
    } else if (extensions.some((ext) => entry.endsWith(ext))) {
      results.push(fullPath)
    }
  }
  return results
}

/** Collect files under a specific subfolder name (e.g. 'components', 'pages') */
function collectFilesInSubfolder(srcDir: string, folderName: string, extensions: string[]): string[] {
  const results: string[] = []
  const allFiles = collectFiles(srcDir, extensions)
  // Normalize path separators for comparison
  const normalizedFolder = folderName.replace(/\\/g, '/')
  for (const file of allFiles) {
    const relPath = relative(srcDir, file).replace(/\\/g, '/')
    // Check if path contains the folder as a directory segment
    const segments = relPath.split('/')
    if (segments.includes(normalizedFolder)) {
      results.push(file)
    }
  }
  return results
}

/** Read file content safely */
function readFileSafe(filePath: string): string {
  try {
    return readFileSync(filePath, 'utf-8')
  } catch {
    return ''
  }
}

/** Get 1-based line number for a character index */
function getLineNumber(content: string, charIndex: number): number {
  let line = 1
  for (let i = 0; i < charIndex && i < content.length; i++) {
    if (content[i] === '\n') line++
  }
  return line
}

// ─── Rule 1: ss/no-direct-fetch ───

function checkNoDirectFetch(srcDir: string): CheckResult[] {
  const results: CheckResult[] = []
  const extensions = ['.tsx', '.ts']

  // Collect files in components/ and pages/ subfolders
  const targetFiles = [
    ...collectFilesInSubfolder(srcDir, 'components', extensions),
    ...collectFilesInSubfolder(srcDir, 'pages', extensions),
  ]

  // Patterns to detect direct fetch/axios usage
  // NOTE: single regex per concern to avoid double-matching on the same line
  const patterns = [
    { regex: /import\s+.*['"]axios['"]/g, label: 'import axios' },
    { regex: /\baxios\s*\.\s*(get|post|put|patch|delete|request)\s*\(/g, label: 'axios call' },
    { regex: /\bfetch\s*\(/g, label: 'fetch call' },
  ]

  for (const file of targetFiles) {
    const content = readFileSafe(file)
    if (!content) continue

    const relFile = relative(srcDir, file).replace(/\\/g, '/')

    for (const { regex, label } of patterns) {
      // Reset regex state
      regex.lastIndex = 0
      let match: RegExpExecArray | null
      while ((match = regex.exec(content)) !== null) {
        const line = getLineNumber(content, match.index)
        results.push({
          code: 'ss/no-direct-fetch',
          status: 'error',
          file: relFile,
          line,
          message: `Direct ${label} in component/page — use service API instead`,
        })
      }
    }
  }

  // If no violations, report ok
  if (results.length === 0) {
    results.push({ code: 'ss/no-direct-fetch', status: 'ok', count: 0 })
  }

  return results
}

// ─── Rule 2: ss/permission-guard ───

function checkPermissionGuard(srcDir: string): CheckResult[] {
  const results: CheckResult[] = []
  const extensions = ['.tsx']

  // Check components and pages
  const targetFiles = [
    ...collectFilesInSubfolder(srcDir, 'components', extensions),
    ...collectFilesInSubfolder(srcDir, 'pages', extensions),
  ]

  // Sensitive action keywords in button/action context
  const sensitiveActions = ['delete', 'remove', 'approve', 'reject', 'execute', 'destroy']
  // Build regex: button or onClick with sensitive text nearby
  // Look for patterns like: <button ... onClick={...delete...} or text content "Delete"
  const closingParen = String.fromCharCode(41)
  const buttonPattern = new RegExp(
    '<button[^>]*>\\s*(?:(?!<\\/button>)[\\s\\S])*?(' + sensitiveActions.join('|') + closingParen,
    'gi',
  )

  for (const file of targetFiles) {
    const content = readFileSafe(file)
    if (!content) continue

    const relFile = relative(srcDir, file).replace(/\\/g, '/')

    // Check if file has PermissionGuard or usePermission
    const hasGuard = content.includes('PermissionGuard') || content.includes('usePermission')

    // Find sensitive buttons
    const lines = content.split('\n')
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i]
      const lineLower = line.toLowerCase()

      // Check for button-like elements with sensitive action text
      for (const action of sensitiveActions) {
        // Match: button text or onClick handler containing the action
        const hasAction = (
          // Button text: >Delete< or > Delete <
          new RegExp('>\\s*' + action + '\\s*<', 'i').test(line) ||
          // onClick handler referencing action: onClick={handleDelete} or onClick={() => delete...}
          new RegExp('onClick=\\{[^}]*' + action, 'i').test(line)
        )

        if (!hasAction) continue

        if (hasGuard) {
          // Check if this specific button is inside a PermissionGuard
          // Look backwards from current line for <PermissionGuard
          let isGuarded = false
          let guardDepth = 0
          for (let j = i; j >= Math.max(0, i - 10); j--) {
            if (lines[j].includes('</PermissionGuard>')) guardDepth++
            if (lines[j].includes('<PermissionGuard')) {
              if (guardDepth === 0) {
                isGuarded = true
                break
              }
              guardDepth--
            }
          }
          // Also check forward for closing tag
          if (isGuarded) {
            let closingFound = false
            for (let j = i; j < Math.min(lines.length, i + 10); j++) {
              if (lines[j].includes('</PermissionGuard>')) {
                closingFound = true
                break
              }
            }
            isGuarded = closingFound
          }

          if (!isGuarded) {
            results.push({
              code: 'ss/permission-guard',
              status: 'error',
              file: relFile,
              line: i + 1,
              message: `Button '${action}' sans PermissionGuard`,
            })
          }
        } else {
          // No guard at all in the file
          results.push({
            code: 'ss/permission-guard',
            status: 'error',
            file: relFile,
            line: i + 1,
            message: `Button '${action}' sans PermissionGuard (aucun guard dans le fichier${closingParen}`,
          })
        }
      }
    }
  }

  if (results.length === 0) {
    results.push({ code: 'ss/permission-guard', status: 'ok', count: 0 })
  }

  return results
}

// ─── Rule 3: ss/dto-naming ───

function checkDtoNaming(srcDir: string): CheckResult[] {
  const results: CheckResult[] = []

  // Target: *.types.ts files in types/ folders
  const allFiles = collectFiles(srcDir, ['.types.ts'])
  const typeFiles = allFiles.filter((f) => {
    const rel = relative(srcDir, f).replace(/\\/g, '/')
    return rel.includes('/types/') || rel.startsWith('types/')
  })

  // Valid DTO patterns
  const validPatterns = [
    /^[A-Z][a-zA-Z0-9]*ResponseDto$/,          // {Resource}ResponseDto
    /^Create[A-Z][a-zA-Z0-9]*Dto$/,             // Create{Resource}Dto
    /^Update[A-Z][a-zA-Z0-9]*Dto$/,             // Update{Resource}Dto
    /^PaginatedResult$/,                          // Generic utility types
    /^PaginationParams$/,
  ]

  // Regex to find exported interfaces
  const interfaceRegex = /export\s+interface\s+([A-Z][a-zA-Z0-9]*)/g

  for (const file of typeFiles) {
    const content = readFileSafe(file)
    if (!content) continue

    const relFile = relative(srcDir, file).replace(/\\/g, '/')

    interfaceRegex.lastIndex = 0
    let match: RegExpExecArray | null
    while ((match = interfaceRegex.exec(content)) !== null) {
      const interfaceName = match[1]
      const isValid = validPatterns.some((pattern) => pattern.test(interfaceName))
      if (!isValid) {
        const line = getLineNumber(content, match.index)
        results.push({
          code: 'ss/dto-naming',
          status: 'warning',
          file: relFile,
          line,
          message: `Interface '${interfaceName}' does not follow DTO naming convention ({Resource}ResponseDto, Create{Resource}Dto, Update{Resource}Dto)`,
        })
      }
    }
  }

  if (results.length === 0) {
    results.push({ code: 'ss/dto-naming', status: 'ok', count: 0 })
  }

  return results
}

// ─── Rule 4: ss/service-hook-structure ───

function checkServiceHookStructure(srcDir: string): CheckResult[] {
  const results: CheckResult[] = []

  // Target: use*.ts files in hooks/ folders
  const allFiles = collectFiles(srcDir, ['.ts'])
  const hookFiles = allFiles.filter((f) => {
    const rel = relative(srcDir, f).replace(/\\/g, '/')
    const name = basename(f)
    return (rel.includes('/hooks/') || rel.startsWith('hooks/')) && name.startsWith('use')
  })

  for (const file of hookFiles) {
    const content = readFileSafe(file)
    if (!content) continue

    const relFile = relative(srcDir, file).replace(/\\/g, '/')

    // Check that hooks use useQuery or useMutation from React Query
    const hasReactQuery = (
      content.includes('useQuery') ||
      content.includes('useMutation')
    )

    // Check for React Query import
    const hasReactQueryImport = (
      content.includes('@tanstack/react-query') ||
      content.includes('react-query')
    )

    if (!hasReactQuery || !hasReactQueryImport) {
      // Find the first export function line
      const exportMatch = /export\s+function\s+use[A-Z]/m.exec(content)
      const line = exportMatch ? getLineNumber(content, exportMatch.index) : 1
      results.push({
        code: 'ss/service-hook-structure',
        status: 'warning',
        file: relFile,
        line,
        message: 'Hook does not use useQuery/useMutation from React Query',
      })
    }
  }

  if (results.length === 0) {
    results.push({ code: 'ss/service-hook-structure', status: 'ok', count: 0 })
  }

  return results
}

// ─── Rule 5: ss/feature-folder ───

function checkFeatureFolder(srcDir: string): CheckResult[] {
  const results: CheckResult[] = []
  const extensions = ['.tsx', '.ts']

  const allFiles = collectFiles(srcDir, extensions)
  const validLayers = ['pages', 'components', 'hooks', 'services', 'types']

  // Expected pattern: features/{module}/{resource}/{layer}/file.ts(x)
  // Or if srcDir itself IS the features dir, then: {module}/{resource}/{layer}/file.ts(x)
  const featurePattern = /features\/[a-z][a-z0-9-]*\/[a-zA-Z][a-zA-Z0-9]*\/(pages|components|hooks|services|types)\//

  // Also accept direct structure: {module}/{resource}/{layer}/ when srcDir points into features
  const directPattern = /^[a-z][a-z0-9-]*\/[a-zA-Z][a-zA-Z0-9]*\/(pages|components|hooks|services|types)\//

  // Accept barrel files at resource root: {module}/{resource}/index.ts
  const barrelPattern = /^[a-z][a-z0-9-]*\/[a-zA-Z][a-zA-Z0-9]*\/index\.ts$/
  const barrelPatternFeatures = /features\/[a-z][a-z0-9-]*\/[a-zA-Z][a-zA-Z0-9]*\/index\.ts$/

  for (const file of allFiles) {
    const relPath = relative(srcDir, file).replace(/\\/g, '/')

    // Skip node_modules, config files, etc.
    if (relPath.startsWith('node_modules/')) continue
    if (relPath.startsWith('.')) continue

    const isValid = (
      featurePattern.test(relPath) ||
      directPattern.test(relPath) ||
      barrelPattern.test(relPath) ||
      barrelPatternFeatures.test(relPath)
    )

    if (!isValid) {
      results.push({
        code: 'ss/feature-folder',
        status: 'warning',
        file: relPath,
        line: 1,
        message: `File not in expected feature folder structure (features/{module}/{resource}/{layer}/)`,
      })
    }
  }

  if (results.length === 0) {
    results.push({ code: 'ss/feature-folder', status: 'ok', count: 0 })
  }

  return results
}

// ─── Public API ───

export function execute(input: ESLintInput): ESLintReport {
  const srcDir = input.src

  // Run all 5 checks
  const checks: CheckResult[] = [
    ...checkNoDirectFetch(srcDir),
    ...checkPermissionGuard(srcDir),
    ...checkDtoNaming(srcDir),
    ...checkServiceHookStructure(srcDir),
    ...checkFeatureFolder(srcDir),
  ]

  // Count errors and warnings
  const errors = checks.filter((c) => c.status === 'error').length
  const warnings = checks.filter((c) => c.status === 'warning').length

  return {
    timestamp: new Date().toISOString(),
    checks,
    errors,
    warnings,
  }
}
