/**
 * @fileoverview ContextSanitizer - Sanitizes project context before sending to LLM
 * @module morph-spec/sanitizer/context-sanitizer
 */

import { minimatch } from 'minimatch';
import {
  WHITELIST_FILES,
  BLACKLIST_DIRECTORIES,
  BLACKLIST_FILES,
  SECRET_PATTERNS,
  MAX_FILE_SIZE,
  MAX_SUMMARY_LENGTH
} from './patterns.js';

/**
 * @typedef {import('../types/index.js').ProjectContext} ProjectContext
 * @typedef {import('../types/index.js').SanitizedContext} SanitizedContext
 */

/**
 * ContextSanitizer - Removes secrets and sensitive data before LLM analysis
 * Implements hybrid whitelist/blacklist approach per ADR-004
 * @class
 */
export class ContextSanitizer {
  /**
   * Sanitize project context (remove secrets, truncate files)
   * @param {ProjectContext} context - Raw project context
   * @returns {SanitizedContext}
   */
  sanitize(context) {
    // Sanitize package.json (remove private fields, redact secrets)
    const sanitizedPackageJson = this.sanitizePackageJson(context.packageJson);

    // Extract only filenames from .csproj paths (not full content)
    const csprojFilenames = context.csprojFiles.map(path => path.split('/').pop());

    // Truncate README and CLAUDE.md to first 500 chars
    const readmeSummary = context.readme
      ? this.truncateText(context.readme, MAX_SUMMARY_LENGTH)
      : null;

    const claudeMdSummary = context.claudeMd
      ? this.truncateText(context.claudeMd, MAX_SUMMARY_LENGTH)
      : null;

    // Sanitize infrastructure summary (remove secrets, just count files)
    const infraSummary = {
      dockerfilesCount: context.infraFiles.dockerfiles.length,
      dockerComposeCount: context.infraFiles.dockerComposeFiles.length,
      bicepFilesCount: context.infraFiles.bicepFiles.length,
      pipelinesCount: context.infraFiles.pipelines.length,
      hasAzure: context.infraFiles.hasAzure,
      hasDocker: context.infraFiles.hasDocker,
      hasDevOps: context.infraFiles.hasDevOps
    };

    // Extract git remote domain only (not full URL with credentials)
    const gitRemoteDomain = context.gitRemote
      ? this.extractDomain(context.gitRemote)
      : null;

    // Estimate total files (approximate)
    const totalFiles = context.structure.topLevelDirs.length * 10; // rough estimate

    return {
      packageJson: sanitizedPackageJson,
      csprojFilenames,
      hasSolution: context.solutionFile !== null,
      readmeSummary,
      claudeMdSummary,
      structure: context.structure,
      infraSummary,
      gitRemoteDomain,
      totalFiles
    };
  }

  /**
   * Sanitize package.json - remove private fields and redact secrets
   * @param {Object|null} packageJson - Raw package.json
   * @returns {Object}
   */
  sanitizePackageJson(packageJson) {
    if (!packageJson) {
      return {};
    }

    // Keep only safe fields
    const safe = {
      name: packageJson.name || 'unknown',
      version: packageJson.version,
      description: packageJson.description,
      type: packageJson.type,
      engines: packageJson.engines
    };

    // Include dependencies (names only, no versions for simplicity)
    if (packageJson.dependencies) {
      safe.dependencies = Object.keys(packageJson.dependencies);
    }

    if (packageJson.devDependencies) {
      safe.devDependencies = Object.keys(packageJson.devDependencies);
    }

    // Include scripts (but redact any that might contain secrets)
    if (packageJson.scripts) {
      safe.scripts = {};
      for (const [key, value] of Object.entries(packageJson.scripts)) {
        safe.scripts[key] = this.redactSecrets(value);
      }
    }

    return safe;
  }

  /**
   * Detect and redact secrets from text
   * @param {string} text - Text to sanitize
   * @returns {string} Sanitized text
   */
  redactSecrets(text) {
    if (!text || typeof text !== 'string') {
      return text;
    }

    let sanitized = text;

    // Apply all secret patterns
    for (const pattern of SECRET_PATTERNS) {
      sanitized = sanitized.replace(pattern.regex, pattern.replacement);
    }

    return sanitized;
  }

  /**
   * Check if file should be excluded from LLM analysis
   * @param {string} filepath - File path (relative or absolute)
   * @returns {boolean} True if file should be excluded
   */
  shouldExcludeFile(filepath) {
    const normalizedPath = filepath.replace(/\\/g, '/');

    // Check if path contains blacklisted directory
    for (const blacklistDir of BLACKLIST_DIRECTORIES) {
      if (normalizedPath.includes(`/${blacklistDir}/`) || normalizedPath.startsWith(`${blacklistDir}/`)) {
        return true;
      }
    }

    // Check if filename matches blacklist pattern
    const filename = normalizedPath.split('/').pop();
    for (const pattern of BLACKLIST_FILES) {
      if (minimatch(filename, pattern, { nocase: true })) {
        return true;
      }
    }

    return false;
  }

  /**
   * Check if file is whitelisted for reading
   * @param {string} filepath - File path
   * @returns {boolean} True if file is whitelisted
   */
  isWhitelisted(filepath) {
    const normalizedPath = filepath.replace(/\\/g, '/');
    const filename = normalizedPath.split('/').pop();

    for (const pattern of WHITELIST_FILES) {
      if (minimatch(filename, pattern, { nocase: true })) {
        return true;
      }
      // Also check full path for patterns like ".github/workflows/*.yml"
      if (minimatch(normalizedPath, pattern, { nocase: true })) {
        return true;
      }
    }

    return false;
  }

  /**
   * Truncate text to maximum length
   * @param {string} text - Text to truncate
   * @param {number} maxLength - Maximum length
   * @returns {string} Truncated text
   */
  truncateText(text, maxLength) {
    if (!text || text.length <= maxLength) {
      return text;
    }

    return text.substring(0, maxLength) + '... [truncated]';
  }

  /**
   * Extract domain from git remote URL
   * @param {string} url - Git remote URL
   * @returns {string|null} Domain only (e.g., "github.com")
   */
  extractDomain(url) {
    try {
      // Handle SSH format: git@github.com:user/repo.git
      if (url.startsWith('git@')) {
        const match = url.match(/git@([^:]+):/);
        return match ? match[1] : null;
      }

      // Handle HTTPS format: https://github.com/user/repo.git
      const urlObj = new URL(url);
      return urlObj.hostname;
    } catch (error) {
      return null;
    }
  }
}
