/**
 * MCP Scanner — MCP Configuration Validator
 * Validates .mcp.json files: server types, trust levels, env vars, unknown fields.
 * Finding IDs: CA-MCP-NNN
 */

import { readTextFile } from './lib/file-discovery.mjs';
import { finding, scannerResult } from './lib/output.mjs';
import { SEVERITY } from './lib/severity.mjs';
import { parseJson } from './lib/yaml-parser.mjs';
import { truncate } from './lib/string-utils.mjs';

const SCANNER = 'MCP';

const VALID_SERVER_TYPES = new Set(['stdio', 'http', 'sse']);
const VALID_TRUST_LEVELS = new Set(['workspace', 'trusted', 'untrusted']);
const VALID_SERVER_FIELDS = new Set([
  'type', 'command', 'args', 'env', 'url', 'headers', 'timeout', 'trust',
]);

const ENV_VAR_PATTERN = /\$\{([^}]+)\}/g;

/**
 * Scan all .mcp.json files discovered.
 * @param {string} targetPath
 * @param {{ files: import('./lib/file-discovery.mjs').ConfigFile[] }} discovery
 * @returns {Promise<object>}
 */
export async function scan(targetPath, discovery) {
  const start = Date.now();
  const mcpFiles = discovery.files.filter(f => f.type === 'mcp-json');
  const findings = [];
  let filesScanned = 0;

  if (mcpFiles.length === 0) {
    return scannerResult(SCANNER, 'skipped', [], 0, Date.now() - start);
  }

  for (const file of mcpFiles) {
    const content = await readTextFile(file.absPath);
    if (!content) continue;
    filesScanned++;

    const parsed = parseJson(content);
    if (!parsed) {
      findings.push(finding({
        scanner: SCANNER,
        severity: SEVERITY.critical,
        title: 'Invalid JSON in MCP config',
        description: `${file.relPath}: Failed to parse as JSON.`,
        file: file.absPath,
        recommendation: 'Fix JSON syntax errors. Use a JSON validator to check the file.',
      }));
      continue;
    }

    const servers = parsed.mcpServers || parsed;
    if (typeof servers !== 'object' || Array.isArray(servers)) continue;

    for (const [name, config] of Object.entries(servers)) {
      if (!config || typeof config !== 'object' || Array.isArray(config)) continue;

      // Check server type
      if (config.type && !VALID_SERVER_TYPES.has(config.type)) {
        findings.push(finding({
          scanner: SCANNER,
          severity: SEVERITY.high,
          title: 'Unknown MCP server type',
          description: `${file.relPath}: Server "${name}" has unknown type "${config.type}".`,
          file: file.absPath,
          evidence: `type: "${config.type}"`,
          recommendation: `Use one of: stdio, http, sse. Got "${config.type}".`,
        }));
      }

      // SSE → HTTP recommendation
      if (config.type === 'sse') {
        findings.push(finding({
          scanner: SCANNER,
          severity: SEVERITY.info,
          title: 'SSE server type — consider HTTP',
          description: `${file.relPath}: Server "${name}" uses "sse" type. The "http" type is the current standard.`,
          file: file.absPath,
          evidence: `type: "sse"`,
          recommendation: 'Migrate from "sse" to "http" type for better compatibility.',
        }));
      }

      // Check trust level
      if (!config.trust) {
        findings.push(finding({
          scanner: SCANNER,
          severity: SEVERITY.medium,
          title: 'Missing trust level',
          description: `${file.relPath}: Server "${name}" has no trust level configured.`,
          file: file.absPath,
          recommendation: 'Add "trust": "workspace"|"trusted"|"untrusted" to explicitly set the trust level.',
        }));
      } else if (!VALID_TRUST_LEVELS.has(config.trust)) {
        findings.push(finding({
          scanner: SCANNER,
          severity: SEVERITY.high,
          title: 'Invalid trust level',
          description: `${file.relPath}: Server "${name}" has invalid trust level "${config.trust}".`,
          file: file.absPath,
          evidence: `trust: "${config.trust}"`,
          recommendation: 'Use one of: workspace, trusted, untrusted.',
        }));
      }

      // Check for env var references in args without env block
      if (Array.isArray(config.args)) {
        for (const arg of config.args) {
          if (typeof arg !== 'string') continue;
          let match;
          ENV_VAR_PATTERN.lastIndex = 0;
          while ((match = ENV_VAR_PATTERN.exec(arg)) !== null) {
            const varName = match[1];
            const hasEnvBlock = config.env && typeof config.env === 'object' && varName in config.env;
            if (!hasEnvBlock) {
              findings.push(finding({
                scanner: SCANNER,
                severity: SEVERITY.medium,
                title: 'Unreferenced env var in args',
                description: `${file.relPath}: Server "${name}" references \${${varName}} in args but has no env block defining it.`,
                file: file.absPath,
                evidence: truncate(arg, 80),
                recommendation: `Add an "env" block with "${varName}" or remove the variable reference.`,
              }));
            }
          }
        }
      }

      // Check for unknown fields
      for (const key of Object.keys(config)) {
        if (!VALID_SERVER_FIELDS.has(key)) {
          findings.push(finding({
            scanner: SCANNER,
            severity: SEVERITY.medium,
            title: 'Unknown MCP server field',
            description: `${file.relPath}: Server "${name}" has unknown field "${key}".`,
            file: file.absPath,
            evidence: `${key}: ${truncate(JSON.stringify(config[key]), 60)}`,
            recommendation: `Remove or correct "${key}". Valid fields: ${[...VALID_SERVER_FIELDS].join(', ')}.`,
          }));
        }
      }
    }
  }

  return scannerResult(SCANNER, 'ok', findings, filesScanned, Date.now() - start);
}
