/**
 * Contract Compliance Validator
 *
 * Parses contracts.cs from feature outputs and verifies that the project code
 * actually implements all defined interfaces, methods, DTOs, and enums.
 *
 * MORPH-SPEC 3.0 - Phase A: Active Validation Pipeline
 */

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

/**
 * Validate that contracts.cs interfaces are implemented in project code
 *
 * @param {string} projectPath - Project root path
 * @param {string} featureName - Feature name
 * @param {Object} options - Options
 * @returns {Object} { status, errors, warnings, issues, coverage }
 */
export async function validateContracts(projectPath, featureName, options = {}) {
  const contractsPath = join(projectPath, '.morph/project/outputs', featureName, 'contracts.cs');

  if (!existsSync(contractsPath)) {
    return {
      status: 'ok',
      errors: 0,
      warnings: 0,
      issues: [],
      coverage: null,
      message: 'No contracts.cs found — skipping'
    };
  }

  const contractsContent = readFileSync(contractsPath, 'utf-8');
  const contracts = parseContracts(contractsContent);

  if (contracts.interfaces.length === 0 && contracts.records.length === 0 && contracts.enums.length === 0) {
    return {
      status: 'ok',
      errors: 0,
      warnings: 1,
      issues: [{ level: 'warning', message: 'contracts.cs exists but no interfaces/records/enums found' }],
      coverage: null
    };
  }

  // Find all .cs files in the project (excluding .morph outputs, bin, obj)
  const csFiles = await glob('**/*.cs', {
    cwd: projectPath,
    ignore: ['.morph/**', '**/bin/**', '**/obj/**', '**/node_modules/**']
  });

  const projectCode = {};
  for (const file of csFiles) {
    const fullPath = join(projectPath, file);
    try {
      projectCode[file] = readFileSync(fullPath, 'utf-8');
    } catch {
      // Skip unreadable files
    }
  }

  const issues = [];
  let implemented = 0;
  let total = 0;
  const interfaceResults = [];

  // Validate interfaces
  for (const iface of contracts.interfaces) {
    total++;
    const result = validateInterface(iface, projectCode, options);
    interfaceResults.push(result);

    if (result.implemented) {
      implemented++;
      if (options.verbose) {
        console.log(chalk.green(`    ✓ ${iface.name} → ${result.implementingClass} (${result.implementedMethods}/${result.totalMethods} methods)`));
      }
    } else {
      issues.push({
        level: 'error',
        message: `Interface ${iface.name} not implemented in project code`,
        solution: `Create a class that implements ${iface.name} with all ${iface.methods.length} method(s)`
      });
    }

    // Check for missing methods
    for (const method of result.missingMethods || []) {
      issues.push({
        level: 'error',
        message: `Method ${method} missing from ${result.implementingClass || iface.name} implementation`,
        file: result.implementingFile,
        solution: `Add method ${method} to the class implementing ${iface.name}`
      });
    }
  }

  // Validate records/DTOs
  for (const record of contracts.records) {
    total++;
    const found = Object.entries(projectCode).some(([, content]) =>
      new RegExp(`(class|record|struct)\\s+${escapeRegex(record.name)}`, 'g').test(content)
    );

    if (found) {
      implemented++;
      if (options.verbose) {
        console.log(chalk.green(`    ✓ ${record.name} (${record.type})`));
      }
    } else {
      issues.push({
        level: 'warning',
        message: `${record.type} ${record.name} defined in contracts but not found in project code`,
        solution: `Create ${record.type} ${record.name} matching the contract definition`
      });
    }
  }

  // Validate enums
  for (const enumDef of contracts.enums) {
    total++;
    const found = Object.entries(projectCode).some(([, content]) =>
      new RegExp(`enum\\s+${escapeRegex(enumDef.name)}`, 'g').test(content)
    );

    if (found) {
      implemented++;
      if (options.verbose) {
        console.log(chalk.green(`    ✓ ${enumDef.name} (enum)`));
      }
    } else {
      issues.push({
        level: 'warning',
        message: `Enum ${enumDef.name} defined in contracts but not found in project code`,
        solution: `Create enum ${enumDef.name} matching the contract definition`
      });
    }
  }

  const coveragePercent = total > 0 ? Math.round((implemented / total) * 100) : 100;
  const errors = issues.filter(i => i.level === 'error').length;
  const warnings = issues.filter(i => i.level === 'warning').length;

  if (options.verbose) {
    console.log(chalk.cyan(`\n    Contract coverage: ${coveragePercent}% (${implemented}/${total})`));
  }

  return {
    status: errors > 0 ? 'error' : 'ok',
    errors,
    warnings,
    issues,
    coverage: {
      percentage: coveragePercent,
      implemented,
      total,
      interfaces: interfaceResults
    }
  };
}

/**
 * Parse contracts.cs content and extract interfaces, records, and enums
 */
function parseContracts(content) {
  const interfaces = [];
  const records = [];
  const enums = [];

  // Extract interfaces with their methods
  const interfaceRegex = /public\s+interface\s+(I\w+)(?:\s*:\s*[\w<>,\s]+)?\s*\{([^}]*)\}/gs;
  let match;

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

    interfaces.push({ name, methods });
  }

  // Extract records/classes (DTOs)
  const recordRegex = /public\s+(record|class|struct)\s+(\w+)/g;
  while ((match = recordRegex.exec(content)) !== null) {
    const type = match[1];
    const name = match[2];
    // Skip interface names that were already captured
    if (!name.startsWith('I') || !interfaces.some(i => i.name === name)) {
      records.push({ type, name });
    }
  }

  // Extract enums
  const enumRegex = /public\s+enum\s+(\w+)/g;
  while ((match = enumRegex.exec(content)) !== null) {
    enums.push({ name: match[1] });
  }

  return { interfaces, records, enums };
}

/**
 * Extract method signatures from an interface body
 */
function extractMethods(body) {
  const methods = [];

  // Match method signatures like: Task<Result> DoSomethingAsync(params);
  const methodRegex = /(?:Task<[^>]+>|Task|void|string|int|bool|decimal|[\w<>[\],\s?]+)\s+(\w+)\s*\(/g;
  let match;

  while ((match = methodRegex.exec(body)) !== null) {
    const methodName = match[1];
    // Skip property getters/setters
    if (!['get', 'set', 'init'].includes(methodName)) {
      methods.push(methodName);
    }
  }

  return methods;
}

/**
 * Validate that an interface is implemented in project code
 */
function validateInterface(iface, projectCode, options) {
  const result = {
    name: iface.name,
    implemented: false,
    implementingClass: null,
    implementingFile: null,
    totalMethods: iface.methods.length,
    implementedMethods: 0,
    missingMethods: []
  };

  // Search for classes implementing this interface
  const implRegex = new RegExp(`class\\s+(\\w+)\\s*(?:<[^>]*>)?\\s*:\\s*[^{]*${escapeRegex(iface.name)}`, 'g');

  for (const [file, content] of Object.entries(projectCode)) {
    const match = implRegex.exec(content);
    if (match) {
      result.implemented = true;
      result.implementingClass = match[1];
      result.implementingFile = file;

      // Check which methods are implemented
      for (const method of iface.methods) {
        const methodExists = new RegExp(`(public|protected|private|internal)?\\s*(async\\s+)?\\S+\\s+${escapeRegex(method)}\\s*\\(`, 'g').test(content);
        if (methodExists) {
          result.implementedMethods++;
        } else {
          result.missingMethods.push(method);
        }
      }

      break; // Found the implementation
    }
    implRegex.lastIndex = 0; // Reset for next file
  }

  return result;
}

/**
 * Escape special regex characters in a string
 */
function escapeRegex(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
