#!/usr/bin/env ts-node
/**
 * Validate Constants Usage - SSOT Enforcement
 *
 * Este script detecta hardcoding de schemas, tablas, rutas API y enums
 * que deberian estar usando las constantes centralizadas del SSOT.
 *
 * Ejecutar: npm run validate:constants
 *
 * @author Architecture-Analyst
 * @date 2025-12-12
 */

import * as fs from 'fs';
import * as path from 'path';

// =============================================================================
// CONFIGURACION
// =============================================================================

interface ValidationPattern {
  pattern: RegExp;
  message: string;
  severity: 'P0' | 'P1' | 'P2';
  suggestion: string;
  exclude?: RegExp[];
}

const PATTERNS: ValidationPattern[] = [
  // Database Schemas
  {
    pattern: /['"`]auth['"`](?!\s*:)/g,
    message: 'Hardcoded schema "auth"',
    severity: 'P0',
    suggestion: 'Usa DB_SCHEMAS.AUTH',
    exclude: [/from\s+['"`]\.\/database\.constants['"`]/],
  },
  {
    pattern: /['"`]construction['"`](?!\s*:)/g,
    message: 'Hardcoded schema "construction"',
    severity: 'P0',
    suggestion: 'Usa DB_SCHEMAS.CONSTRUCTION',
  },
  {
    pattern: /['"`]hr['"`](?!\s*:)(?!\.entity)/g,
    message: 'Hardcoded schema "hr"',
    severity: 'P0',
    suggestion: 'Usa DB_SCHEMAS.HR',
  },
  {
    pattern: /['"`]hse['"`](?!\s*:)(?!\/)/g,
    message: 'Hardcoded schema "hse"',
    severity: 'P0',
    suggestion: 'Usa DB_SCHEMAS.HSE',
  },
  {
    pattern: /['"`]estimates['"`](?!\s*:)/g,
    message: 'Hardcoded schema "estimates"',
    severity: 'P0',
    suggestion: 'Usa DB_SCHEMAS.ESTIMATES',
  },
  {
    pattern: /['"`]infonavit['"`](?!\s*:)/g,
    message: 'Hardcoded schema "infonavit"',
    severity: 'P0',
    suggestion: 'Usa DB_SCHEMAS.INFONAVIT',
  },
  {
    pattern: /['"`]inventory['"`](?!\s*:)/g,
    message: 'Hardcoded schema "inventory"',
    severity: 'P0',
    suggestion: 'Usa DB_SCHEMAS.INVENTORY',
  },
  {
    pattern: /['"`]purchase['"`](?!\s*:)/g,
    message: 'Hardcoded schema "purchase"',
    severity: 'P0',
    suggestion: 'Usa DB_SCHEMAS.PURCHASE',
  },

  // API Routes
  {
    pattern: /['"`]\/api\/v1\/proyectos['"`]/g,
    message: 'Hardcoded API route "/api/v1/proyectos"',
    severity: 'P0',
    suggestion: 'Usa API_ROUTES.PROYECTOS.BASE',
  },
  {
    pattern: /['"`]\/api\/v1\/fraccionamientos['"`]/g,
    message: 'Hardcoded API route "/api/v1/fraccionamientos"',
    severity: 'P0',
    suggestion: 'Usa API_ROUTES.FRACCIONAMIENTOS.BASE',
  },
  {
    pattern: /['"`]\/api\/v1\/employees['"`]/g,
    message: 'Hardcoded API route "/api/v1/employees"',
    severity: 'P0',
    suggestion: 'Usa API_ROUTES.EMPLOYEES.BASE',
  },
  {
    pattern: /['"`]\/api\/v1\/incidentes['"`]/g,
    message: 'Hardcoded API route "/api/v1/incidentes"',
    severity: 'P0',
    suggestion: 'Usa API_ROUTES.INCIDENTES.BASE',
  },

  // Common Table Names
  {
    pattern: /FROM\s+proyectos(?!\s+AS|\s+WHERE)/gi,
    message: 'Hardcoded table name "proyectos"',
    severity: 'P1',
    suggestion: 'Usa DB_TABLES.CONSTRUCTION.PROYECTOS',
  },
  {
    pattern: /FROM\s+fraccionamientos(?!\s+AS|\s+WHERE)/gi,
    message: 'Hardcoded table name "fraccionamientos"',
    severity: 'P1',
    suggestion: 'Usa DB_TABLES.CONSTRUCTION.FRACCIONAMIENTOS',
  },
  {
    pattern: /FROM\s+employees(?!\s+AS|\s+WHERE)/gi,
    message: 'Hardcoded table name "employees"',
    severity: 'P1',
    suggestion: 'Usa DB_TABLES.HR.EMPLOYEES',
  },
  {
    pattern: /FROM\s+incidentes(?!\s+AS|\s+WHERE)/gi,
    message: 'Hardcoded table name "incidentes"',
    severity: 'P1',
    suggestion: 'Usa DB_TABLES.HSE.INCIDENTES',
  },

  // Status Values
  {
    pattern: /status\s*===?\s*['"`]active['"`]/gi,
    message: 'Hardcoded status "active"',
    severity: 'P1',
    suggestion: 'Usa PROJECT_STATUS.ACTIVE o USER_STATUS.ACTIVE',
  },
  {
    pattern: /status\s*===?\s*['"`]borrador['"`]/gi,
    message: 'Hardcoded status "borrador"',
    severity: 'P1',
    suggestion: 'Usa BUDGET_STATUS.DRAFT o ESTIMATION_STATUS.DRAFT',
  },
  {
    pattern: /status\s*===?\s*['"`]aprobado['"`]/gi,
    message: 'Hardcoded status "aprobado"',
    severity: 'P1',
    suggestion: 'Usa BUDGET_STATUS.APPROVED o ESTIMATION_STATUS.APPROVED',
  },

  // Role Names
  {
    pattern: /role\s*===?\s*['"`]admin['"`]/gi,
    message: 'Hardcoded role "admin"',
    severity: 'P0',
    suggestion: 'Usa ROLES.ADMIN',
  },
  {
    pattern: /role\s*===?\s*['"`]supervisor['"`]/gi,
    message: 'Hardcoded role "supervisor"',
    severity: 'P1',
    suggestion: 'Usa ROLES.SUPERVISOR_OBRA o ROLES.SUPERVISOR_HSE',
  },
];

// Archivos a excluir
const EXCLUDED_PATHS = [
  'node_modules',
  'dist',
  '.git',
  'coverage',
  'database.constants.ts',
  'api.constants.ts',
  'enums.constants.ts',
  'index.ts',
  '.sql',
  '.md',
  '.json',
  '.yml',
  '.yaml',
];

// Extensiones a validar
const VALID_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];

// =============================================================================
// TIPOS
// =============================================================================

interface Violation {
  file: string;
  line: number;
  column: number;
  pattern: string;
  message: string;
  severity: 'P0' | 'P1' | 'P2';
  suggestion: string;
  context: string;
}

// =============================================================================
// FUNCIONES
// =============================================================================

function shouldExclude(filePath: string): boolean {
  return EXCLUDED_PATHS.some(excluded => filePath.includes(excluded));
}

function hasValidExtension(filePath: string): boolean {
  return VALID_EXTENSIONS.some(ext => filePath.endsWith(ext));
}

function getFiles(dir: string): string[] {
  const files: string[] = [];

  if (!fs.existsSync(dir)) {
    return files;
  }

  const items = fs.readdirSync(dir);

  for (const item of items) {
    const fullPath = path.join(dir, item);
    const stat = fs.statSync(fullPath);

    if (stat.isDirectory()) {
      if (!shouldExclude(fullPath)) {
        files.push(...getFiles(fullPath));
      }
    } else if (stat.isFile() && hasValidExtension(fullPath) && !shouldExclude(fullPath)) {
      files.push(fullPath);
    }
  }

  return files;
}

function findViolations(filePath: string, content: string, patterns: ValidationPattern[]): Violation[] {
  const violations: Violation[] = [];
  const lines = content.split('\n');

  for (const patternConfig of patterns) {
    let match: RegExpExecArray | null;
    const regex = new RegExp(patternConfig.pattern.source, patternConfig.pattern.flags);

    while ((match = regex.exec(content)) !== null) {
      // Check exclusions
      if (patternConfig.exclude) {
        const shouldSkip = patternConfig.exclude.some(excludePattern =>
          excludePattern.test(content)
        );
        if (shouldSkip) continue;
      }

      // Find line number
      const beforeMatch = content.substring(0, match.index);
      const lineNumber = beforeMatch.split('\n').length;
      const lineStart = beforeMatch.lastIndexOf('\n') + 1;
      const column = match.index - lineStart + 1;

      violations.push({
        file: filePath,
        line: lineNumber,
        column,
        pattern: match[0],
        message: patternConfig.message,
        severity: patternConfig.severity,
        suggestion: patternConfig.suggestion,
        context: lines[lineNumber - 1]?.trim() || '',
      });
    }
  }

  return violations;
}

function formatViolation(v: Violation): string {
  const severityColor = {
    P0: '\x1b[31m', // Red
    P1: '\x1b[33m', // Yellow
    P2: '\x1b[36m', // Cyan
  };
  const reset = '\x1b[0m';

  return `
${severityColor[v.severity]}[${v.severity}]${reset} ${v.message}
  File: ${v.file}:${v.line}:${v.column}
  Found: "${v.pattern}"
  Context: ${v.context}
  Suggestion: ${v.suggestion}
`;
}

function generateReport(violations: Violation[]): void {
  const p0 = violations.filter(v => v.severity === 'P0');
  const p1 = violations.filter(v => v.severity === 'P1');
  const p2 = violations.filter(v => v.severity === 'P2');

  console.log('\n========================================');
  console.log('SSOT VALIDATION REPORT');
  console.log('========================================\n');

  console.log(`Total Violations: ${violations.length}`);
  console.log(`  P0 (Critical): ${p0.length}`);
  console.log(`  P1 (High):     ${p1.length}`);
  console.log(`  P2 (Medium):   ${p2.length}`);

  if (violations.length > 0) {
    console.log('\n----------------------------------------');
    console.log('VIOLATIONS FOUND:');
    console.log('----------------------------------------');

    // Group by file
    const byFile = violations.reduce((acc, v) => {
      if (!acc[v.file]) acc[v.file] = [];
      acc[v.file].push(v);
      return acc;
    }, {} as Record<string, Violation[]>);

    for (const [file, fileViolations] of Object.entries(byFile)) {
      console.log(`\n📁 ${file}`);
      for (const v of fileViolations) {
        console.log(formatViolation(v));
      }
    }
  }

  console.log('\n========================================');

  if (p0.length > 0) {
    console.log('\n❌ FAILED: P0 violations found. Fix before merging.\n');
    process.exit(1);
  } else if (violations.length > 0) {
    console.log('\n⚠️  WARNING: Non-critical violations found. Consider fixing.\n');
    process.exit(0);
  } else {
    console.log('\n✅ PASSED: No SSOT violations found!\n');
    process.exit(0);
  }
}

// =============================================================================
// MAIN
// =============================================================================

function main(): void {
  const backendDir = path.resolve(__dirname, '../../backend/src');
  const frontendDir = path.resolve(__dirname, '../../frontend/web/src');

  console.log('🔍 Validating SSOT constants usage...\n');
  console.log(`Backend:  ${backendDir}`);
  console.log(`Frontend: ${frontendDir}`);

  const allViolations: Violation[] = [];

  // Scan backend
  if (fs.existsSync(backendDir)) {
    const backendFiles = getFiles(backendDir);
    console.log(`\nScanning ${backendFiles.length} backend files...`);

    for (const file of backendFiles) {
      const content = fs.readFileSync(file, 'utf-8');
      const violations = findViolations(file, content, PATTERNS);
      allViolations.push(...violations);
    }
  }

  // Scan frontend
  if (fs.existsSync(frontendDir)) {
    const frontendFiles = getFiles(frontendDir);
    console.log(`Scanning ${frontendFiles.length} frontend files...`);

    for (const file of frontendFiles) {
      const content = fs.readFileSync(file, 'utf-8');
      const violations = findViolations(file, content, PATTERNS);
      allViolations.push(...violations);
    }
  }

  generateReport(allViolations);
}

main();
