/**
 * Spec Validator
 *
 * Validates spec.md and contracts.cs at design time (Phase 2) to catch
 * architectural and coding standard violations early.
 *
 * MORPH-SPEC 3.0 - Phase 4: Spec-Time Validation
 */

import { readFileSync, existsSync } from 'fs';
import { join } from 'path';

/**
 * Validate spec.md and contracts.cs at design time
 *
 * @param {string} projectPath - Project root path
 * @param {string} featureName - Feature name
 * @param {Object} options - Options
 * @returns {Object} { status, errors, warnings, issues }
 */
export async function validateSpec(projectPath, featureName, options = {}) {
  const outputsPath = join(projectPath, '.morph/project/outputs', featureName);
  const specPath = join(outputsPath, 'spec.md');
  const contractsPath = join(outputsPath, 'contracts.cs');

  const issues = [];

  // Validate spec.md
  if (existsSync(specPath)) {
    const specContent = readFileSync(specPath, 'utf8');
    const specIssues = validateSpecMd(specContent);
    issues.push(...specIssues);
  }

  // Validate contracts.cs
  if (existsSync(contractsPath)) {
    const contractsContent = readFileSync(contractsPath, 'utf8');
    const contractsIssues = validateContractsCs(contractsContent);
    issues.push(...contractsIssues);
  }

  const errors = issues.filter(i => i.level === 'error').length;
  const warnings = issues.filter(i => i.level === 'warning').length;

  return {
    status: errors > 0 ? 'error' : (warnings > 0 ? 'warning' : 'ok'),
    errors,
    warnings,
    issues,
    passed: errors === 0
  };
}

/**
 * Validate spec.md structure and content
 */
function validateSpecMd(content) {
  const issues = [];

  // Required sections
  const requiredSections = [
    { name: 'Overview', pattern: /^##\s+(?:1\.\s+)?Overview/mi },
    { name: 'Functional Requirements', pattern: /^##\s+(?:\d+\.\s+)?Functional Requirements/mi },
    { name: 'Non-Functional Requirements', pattern: /^##\s+(?:\d+\.\s+)?Non-Functional Requirements/mi },
    { name: 'Technical Architecture', pattern: /^##\s+(?:\d+\.\s+)?Technical Architecture/mi },
    { name: 'Data Model', pattern: /^##\s+(?:\d+\.\s+)?Data Model/mi }
  ];

  for (const section of requiredSections) {
    if (!section.pattern.test(content)) {
      issues.push({
        level: 'error',
        type: 'spec',
        message: `Missing required section: "${section.name}"`,
        solution: `Add ## ${section.name} section to spec.md`
      });
    }
  }

  // Check for manual infrastructure creation (anti-pattern)
  const manualInfraPatterns = [
    /create.*manually/i,
    /portal.*azure/i,
    /click.*portal/i,
    /via.*portal/i,
    /manual.*setup/i
  ];

  for (const pattern of manualInfraPatterns) {
    if (pattern.test(content)) {
      issues.push({
        level: 'error',
        type: 'spec',
        message: 'Spec references manual Azure portal creation (anti-pattern)',
        solution: 'Use Infrastructure as Code (Bicep) instead of manual portal operations'
      });
      break; // One error is enough
    }
  }

  // Check for Bicep reference (best practice)
  const hasBicep = /bicep/i.test(content) || /infrastructure\s+as\s+code/i.test(content) || /\.bicep/i.test(content);
  if (!hasBicep && /azure|sql|storage|container/i.test(content)) {
    issues.push({
      level: 'warning',
      type: 'spec',
      message: 'Spec mentions Azure resources but no Bicep/IaC reference found',
      solution: 'Add Infrastructure section with Bicep module references'
    });
  }

  return issues;
}

/**
 * Validate contracts.cs coding standards
 */
function validateContractsCs(content) {
  const issues = [];

  // 1. Interface naming: Must start with 'I' and be PascalCase
  const interfaceRegex = /public\s+interface\s+(\w+)/g;
  let match;

  while ((match = interfaceRegex.exec(content)) !== null) {
    const name = match[1];

    if (!name.startsWith('I')) {
      issues.push({
        level: 'error',
        type: 'contracts',
        message: `Interface '${name}' must start with 'I' (e.g., I${name})`,
        solution: `Rename to I${name}`
      });
    } else if (!isPascalCase(name)) {
      issues.push({
        level: 'error',
        type: 'contracts',
        message: `Interface '${name}' must use PascalCase`,
        solution: `Rename to ${toPascalCase(name)}`
      });
    }
  }

  // 2. Async methods must have CancellationToken parameter
  const asyncMethodRegex = /Task(?:<[^>]+>)?\s+(\w+Async)\s*\(([^)]*)\)/g;
  while ((match = asyncMethodRegex.exec(content)) !== null) {
    const methodName = match[1];
    const params = match[2];

    if (!params.includes('CancellationToken')) {
      issues.push({
        level: 'error',
        type: 'contracts',
        message: `Async method '${methodName}' missing CancellationToken parameter`,
        solution: `Add 'CancellationToken ct = default' parameter to ${methodName}`
      });
    }
  }

  // 3. DTOs should use records (not classes)
  const classRegex = /public\s+class\s+(\w+(?:Dto|Response|Request|Command|Query))/g;
  while ((match = classRegex.exec(content)) !== null) {
    const className = match[1];
    issues.push({
      level: 'warning',
      type: 'contracts',
      message: `DTO '${className}' defined as class instead of record`,
      solution: `Use 'public record ${className}' for immutable DTOs`
    });
  }

  // 4. Classes should be sealed (unless abstract/base)
  const unsealedClassRegex = /public\s+class\s+(\w+)(?!\s*:\s*\w+Base)/g;
  while ((match = unsealedClassRegex.exec(content)) !== null) {
    const className = match[1];
    // Skip if already sealed
    const lineStart = content.lastIndexOf('\n', match.index) + 1;
    const lineEnd = content.indexOf('\n', match.index);
    const line = content.substring(lineStart, lineEnd);

    if (!line.includes('sealed') && !line.includes('abstract')) {
      issues.push({
        level: 'warning',
        type: 'contracts',
        message: `Class '${className}' should be sealed (unless designed for inheritance)`,
        solution: `Change to 'public sealed class ${className}'`
      });
    }
  }

  // 5. PascalCase for constants (NOT UPPER_SNAKE_CASE)
  const upperSnakeRegex = /const\s+\w+\s+([A-Z_][A-Z0-9_]+)\s*=/g;
  while ((match = upperSnakeRegex.exec(content)) !== null) {
    const constName = match[1];
    if (constName.includes('_') && constName === constName.toUpperCase()) {
      issues.push({
        level: 'error',
        type: 'contracts',
        message: `Constant '${constName}' uses UPPER_SNAKE_CASE (anti-pattern in C#)`,
        solution: `Use PascalCase: '${toPascalCase(constName)}'`
      });
    }
  }

  // 6. Methods returning Task must end with Async
  const taskMethodRegex = /Task(?:<[^>]+>)?\s+(\w+)\s*\(/g;
  while ((match = taskMethodRegex.exec(content)) !== null) {
    const methodName = match[1];
    if (!methodName.endsWith('Async') && !['get', 'set', 'init'].includes(methodName)) {
      issues.push({
        level: 'error',
        type: 'contracts',
        message: `Method '${methodName}' returns Task but doesn't end with 'Async'`,
        solution: `Rename to '${methodName}Async'`
      });
    }
  }

  // 7. Enum members should be PascalCase (NOT UPPER_SNAKE_CASE)
  const enumRegex = /enum\s+\w+\s*\{([^}]+)\}/gs;
  while ((match = enumRegex.exec(content)) !== null) {
    const enumBody = match[1];
    const memberRegex = /\n\s*([A-Z_][A-Z0-9_]+)/g;
    let memberMatch;

    while ((memberMatch = memberRegex.exec(enumBody)) !== null) {
      const memberName = memberMatch[1];
      if (memberName.includes('_') && memberName === memberName.toUpperCase()) {
        issues.push({
          level: 'error',
          type: 'contracts',
          message: `Enum member '${memberName}' uses UPPER_SNAKE_CASE (must be PascalCase)`,
          solution: `Use PascalCase: '${toPascalCase(memberName)}'`
        });
      }
    }
  }

  return issues;
}

/**
 * Check if a string is PascalCase
 */
function isPascalCase(str) {
  return /^[A-Z][a-z0-9]*([A-Z][a-z0-9]*)*$/.test(str);
}

/**
 * Convert string to PascalCase
 */
function toPascalCase(str) {
  return str
    .split('_')
    .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
    .join('');
}
