// engine/detectors/monitoring-detector.ts — Monitoring/observability stack detection (SPEC-026a)
// Detects Prometheus, Datadog, New Relic, OpenTelemetry, CloudWatch, Sentry, and more.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { glob } from 'glob';
// === Detection registry ===
const OBS_SIGNALS = [
    {
        tool: 'prometheus',
        categories: ['metrics', 'alerting'],
        configFiles: ['prometheus.yml', 'prometheus.yaml'],
        globPatterns: ['**/prometheus*.yml', '**/alerts/*.yml', '**/alert_rules*.yml'],
    },
    {
        tool: 'grafana',
        categories: ['metrics', 'alerting'],
        configFiles: ['grafana.ini', '.grafana'],
        globPatterns: ['**/grafana/**/*.json', '**/dashboards/*.json'],
    },
    {
        tool: 'datadog',
        categories: ['metrics', 'tracing', 'logging', 'alerting'],
        packages: ['dd-trace', 'datadog-metrics', 'hot-shots'],
        configFiles: ['datadog.yaml', '.datadog'],
        envVarPatterns: ['DD_API_KEY', 'DD_AGENT_HOST', 'DATADOG_API_KEY'],
    },
    {
        tool: 'new-relic',
        categories: ['metrics', 'tracing', 'logging'],
        packages: ['newrelic', '@newrelic/native-metrics', '@newrelic/next'],
        configFiles: ['newrelic.js', 'newrelic.cjs', 'newrelic.ts'],
        envVarPatterns: ['NEW_RELIC_LICENSE_KEY', 'NEW_RELIC_APP_NAME'],
    },
    {
        tool: 'opentelemetry',
        categories: ['metrics', 'tracing', 'logging'],
        packages: [
            '@opentelemetry/api',
            '@opentelemetry/sdk-node',
            '@opentelemetry/auto-instrumentations-node',
            'opentelemetry-sdk-trace-web',
        ],
        configFiles: ['otel-collector.yaml', 'opentelemetry.yaml'],
    },
    {
        tool: 'cloudwatch',
        categories: ['metrics', 'logging', 'alerting'],
        packages: ['@aws-sdk/client-cloudwatch', 'aws-cloudwatch-metric'],
        configFiles: ['.aws/config'],
        envVarPatterns: ['AWS_CLOUDWATCH_GROUP', 'CLOUDWATCH_LOG_GROUP'],
    },
    {
        tool: 'sentry',
        categories: ['errorTracking'],
        packages: [
            '@sentry/node',
            '@sentry/react',
            '@sentry/nextjs',
            '@sentry/browser',
            '@sentry/vue',
            '@sentry/angular',
        ],
        configFiles: ['.sentryclirc', 'sentry.properties'],
        globPatterns: ['**/sentry.*.config.*'],
    },
    {
        tool: 'bugsnag',
        categories: ['errorTracking'],
        packages: ['@bugsnag/js', 'bugsnag', '@bugsnag/node'],
        envVarPatterns: ['BUGSNAG_API_KEY'],
    },
    {
        tool: 'rollbar',
        categories: ['errorTracking'],
        packages: ['rollbar'],
        envVarPatterns: ['ROLLBAR_ACCESS_TOKEN'],
    },
    {
        tool: 'honeycomb',
        categories: ['tracing'],
        packages: ['@honeycombio/opentelemetry-node', 'honeycomb-opentelemetry-web'],
        configFiles: ['.honeycomb'],
        envVarPatterns: ['HONEYCOMB_API_KEY', 'HONEYCOMB_TEAM'],
    },
    {
        tool: 'jaeger',
        categories: ['tracing'],
        packages: ['jaeger-client', 'opentracing'],
        configFiles: ['jaeger.yaml'],
        globPatterns: ['**/jaeger*.yml'],
    },
    {
        tool: 'zipkin',
        categories: ['tracing'],
        packages: ['zipkin', 'zipkin-instrumentation-express'],
        envVarPatterns: ['ZIPKIN_URL', 'ZIPKIN_BASE_URL'],
    },
    {
        tool: 'elk-stack',
        categories: ['logging'],
        packages: ['@elastic/ecs-pino-format', 'winston-elasticsearch'],
        configFiles: ['logstash.conf', 'kibana.yml'],
        envVarPatterns: ['ELASTICSEARCH_URL', 'ELASTIC_APM_SERVER_URL'],
    },
    {
        tool: 'loki',
        categories: ['logging'],
        packages: ['pino-loki', 'winston-loki'],
        configFiles: ['loki.yaml'],
        envVarPatterns: ['LOKI_URL'],
    },
    {
        tool: 'signoz',
        categories: ['metrics', 'tracing', 'logging'],
        packages: ['@signoz/opentelemetry-js'],
        envVarPatterns: ['SIGNOZ_ACCESS_TOKEN', 'OTEL_EXPORTER_OTLP_ENDPOINT'],
    },
];
// === Public API ===
/**
 * Detects observability and monitoring stack in a project.
 */
export async function detectMonitoring(projectPath) {
    const stack = {
        metrics: [],
        tracing: [],
        logging: [],
        errorTracking: [],
        alerting: [],
    };
    const foundConfigFiles = [];
    // Read package.json dependencies once
    const deps = await readPackageDeps(projectPath);
    // Read .env content once for env var patterns
    const envContent = await readEnvContent(projectPath);
    for (const signal of OBS_SIGNALS) {
        const detected = await isToolDetected(projectPath, signal, deps, envContent, foundConfigFiles);
        if (detected) {
            for (const category of signal.categories) {
                if (!stack[category].includes(signal.tool)) {
                    stack[category].push(signal.tool);
                }
            }
        }
    }
    // Check for SLO definitions
    const hasSlo = await hasGlobMatches(projectPath, [
        '**/slo*.yaml',
        '**/slo*.yml',
        '**/service-level-objectives*',
    ]);
    // Check for alert rules
    const hasAlertRules = await hasGlobMatches(projectPath, [
        '**/alert_rules*.yml',
        '**/alerts/*.yml',
        '**/alerting-rules*.yaml',
        '**/runbooks/**/*.md',
    ]);
    // Check for dashboards
    const hasDashboards = await hasGlobMatches(projectPath, [
        '**/dashboards/*.json',
        '**/grafana/**/*.json',
    ]);
    return {
        detected: stack,
        hasSlo,
        hasAlertRules,
        hasDashboards,
        configFiles: [...new Set(foundConfigFiles)],
    };
}
// === Internal helpers ===
async function isToolDetected(projectPath, signal, deps, envContent, foundFiles) {
    // Check npm packages
    if (signal.packages) {
        for (const pkg of signal.packages) {
            if (deps.has(pkg)) {
                return true;
            }
        }
    }
    // Check config files
    if (signal.configFiles) {
        /* v8 ignore start */
        for (const f of signal.configFiles) {
            if (await fileExists(join(projectPath, f))) {
                foundFiles.push(f);
                return true;
            }
        }
        /* v8 ignore stop */
    }
    // Check glob patterns
    if (signal.globPatterns) {
        for (const pattern of signal.globPatterns) {
            const matches = await safeGlob(projectPath, pattern);
            if (matches.length > 0) {
                foundFiles.push(...matches);
                return true;
            }
        }
    }
    // Check env var patterns
    /* v8 ignore next 1 */
    if (signal.envVarPatterns && envContent.length > 0) {
        for (const pattern of signal.envVarPatterns) {
            if (envContent.includes(pattern)) {
                return true;
            }
        }
    }
    return false;
}
async function readPackageDeps(projectPath) {
    try {
        const content = await readFile(join(projectPath, 'package.json'), 'utf-8');
        const pkg = JSON.parse(content);
        return new Set([
            ...Object.keys(pkg.dependencies ?? {}),
            ...Object.keys(pkg.devDependencies ?? {}),
        ]);
    }
    catch {
        return new Set();
    }
}
async function readEnvContent(projectPath) {
    const envFiles = ['.env', '.env.example', '.env.local.example', '.env.production'];
    const contents = [];
    for (const f of envFiles) {
        try {
            const c = await readFile(join(projectPath, f), 'utf-8');
            contents.push(c);
        }
        catch {
            // ignore missing files
        }
    }
    return contents.join('\n');
}
async function hasGlobMatches(projectPath, patterns) {
    for (const pattern of patterns) {
        const matches = await safeGlob(projectPath, pattern);
        if (matches.length > 0) {
            return true;
        }
    }
    return false;
}
async function safeGlob(projectPath, pattern) {
    try {
        return await glob(pattern, { cwd: projectPath, nodir: true });
    }
    catch {
        return [];
    }
}
async function fileExists(filePath) {
    try {
        await readFile(filePath, 'utf-8');
        return true;
        /* v8 ignore next 2 */
    }
    catch {
        return false;
    }
}
//# sourceMappingURL=monitoring-detector.js.map