// engine/verify-integrations/index.ts — SPEC-965
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
/**
 * Verify that configured external API integrations are alive and responding.
 */
export async function verifyIntegrations(input) {
    const { projectPath, endpoints: explicitEndpoints, timeout = 10000, failFast = false } = input;
    // Load config file if present
    const configEndpoints = await loadConfig(projectPath);
    const endpoints = explicitEndpoints && explicitEndpoints.length > 0 ? explicitEndpoints : configEndpoints;
    if (endpoints.length === 0) {
        const report = {
            pass: true,
            totalEndpoints: 0,
            passedCount: 0,
            failedCount: 0,
            totalDurationMs: 0,
            results: [],
        };
        return {
            report,
            humanSummary: 'No integrations configured. Create `.planu/integrations.json` or pass `endpoints`.',
        };
    }
    const results = [];
    const startTime = Date.now();
    for (const ep of endpoints) {
        const result = await pingEndpoint(ep, timeout);
        results.push(result);
        if (failFast && !result.pass) {
            break;
        }
    }
    const totalDurationMs = Date.now() - startTime;
    const passedCount = results.filter((r) => r.pass).length;
    const failedCount = results.length - passedCount;
    const pass = failedCount === 0;
    const report = {
        pass,
        totalEndpoints: results.length,
        passedCount,
        failedCount,
        totalDurationMs,
        results,
    };
    const humanSummary = buildHumanSummary(report);
    return { report, humanSummary };
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
async function loadConfig(projectPath) {
    const configPath = join(projectPath, '.planu', 'integrations.json');
    try {
        const raw = await readFile(configPath, 'utf-8');
        const parsed = JSON.parse(raw);
        return Array.isArray(parsed.endpoints) ? parsed.endpoints : [];
    }
    catch {
        return [];
    }
}
async function checkSchema(response, responseSchema) {
    try {
        const body = await response.json();
        const validation = validateJsonSchema(body, responseSchema);
        return {
            valid: validation.valid,
            errors: validation.errors,
        };
    }
    catch {
        return { valid: false, errors: ['Response body is not valid JSON'] };
    }
}
async function validateResponse(response, ep, latencyMs) {
    const { name, url, expectedStatus = 200, method = 'HEAD', responseSchema, expectedContentType, } = ep;
    // Status check
    const statusOk = response.status === expectedStatus ||
        (expectedStatus >= 200 &&
            expectedStatus < 300 &&
            response.status >= 200 &&
            response.status < 300);
    if (!statusOk) {
        return {
            name,
            url,
            pass: false,
            statusCode: response.status,
            latencyMs,
            error: `Expected status ${expectedStatus}, got ${response.status}`,
        };
    }
    // Content-Type check
    if (expectedContentType) {
        const contentType = response.headers.get('content-type') ?? '';
        if (!contentType.includes(expectedContentType)) {
            return {
                name,
                url,
                pass: false,
                statusCode: response.status,
                latencyMs,
                error: `Expected Content-Type '${expectedContentType}', got '${contentType}'`,
            };
        }
    }
    // Schema validation (only for GET/POST with body)
    let schemaValid;
    let schemaErrors;
    if (responseSchema && method !== 'HEAD') {
        const result = await checkSchema(response, responseSchema);
        schemaValid = result.valid;
        schemaErrors = result.errors;
        if (!schemaValid) {
            return {
                name,
                url,
                pass: false,
                statusCode: response.status,
                latencyMs,
                schemaValid: false,
                schemaErrors,
                error: `Schema validation failed: ${schemaErrors?.join('; ') ?? 'Unknown error'}`,
            };
        }
    }
    return {
        name,
        url,
        pass: true,
        statusCode: response.status,
        latencyMs,
        schemaValid,
        schemaErrors,
    };
}
async function pingEndpoint(ep, timeout) {
    const { name, url, method = 'HEAD', authorization } = ep;
    const controller = new AbortController();
    const timer = setTimeout(() => {
        controller.abort();
    }, timeout);
    const start = Date.now();
    try {
        const headers = {};
        if (authorization) {
            headers.Authorization = authorization;
        }
        const response = await fetch(url, {
            method,
            headers,
            signal: controller.signal,
        });
        clearTimeout(timer);
        const latencyMs = Date.now() - start;
        return await validateResponse(response, ep, latencyMs);
    }
    catch (err) {
        clearTimeout(timer);
        const latencyMs = Date.now() - start;
        const errorMessage = err instanceof Error ? err.message : String(err);
        return {
            name,
            url,
            pass: false,
            statusCode: 0,
            latencyMs,
            error: errorMessage.includes('abort') ? `Timeout after ${timeout}ms` : errorMessage,
        };
    }
}
/**
 * Minimal JSON schema validation (subset of JSON Schema Draft 7).
 * Only supports: type, required, properties.
 */
function validateJsonSchema(value, schema) {
    const errors = [];
    function validate(val, sch, path) {
        // type check
        if (sch.type) {
            const expectedType = sch.type;
            const actualType = val === null ? 'null' : Array.isArray(val) ? 'array' : typeof val;
            if (expectedType !== actualType) {
                errors.push(`${path}: expected type ${expectedType}, got ${actualType}`);
                return;
            }
        }
        // required properties
        if (sch.required &&
            Array.isArray(sch.required) &&
            val &&
            typeof val === 'object' &&
            !Array.isArray(val)) {
            for (const key of sch.required) {
                if (!(key in val)) {
                    errors.push(`${path}: missing required property '${String(key)}'`);
                }
            }
        }
        // nested properties
        if (sch.properties &&
            typeof sch.properties === 'object' &&
            val &&
            typeof val === 'object' &&
            !Array.isArray(val)) {
            const props = sch.properties;
            for (const [key, subSchema] of Object.entries(props)) {
                if (key in val) {
                    validate(val[key], subSchema, `${path}.${key}`);
                }
            }
        }
    }
    validate(value, schema, '$');
    return { valid: errors.length === 0, errors };
}
function buildHumanSummary(report) {
    const lines = [
        `# Integration Health Check`,
        '',
        `**Result**: ${report.pass ? 'PASS' : 'FAIL'}`,
        `**Endpoints**: ${report.totalEndpoints} (${report.passedCount} passed, ${report.failedCount} failed)`,
        `**Total time**: ${report.totalDurationMs}ms`,
        '',
    ];
    if (report.results.length > 0) {
        lines.push('## Results');
        lines.push('');
        for (const r of report.results) {
            const icon = r.pass ? '✅' : '❌';
            lines.push(`${icon} **${r.name}** — \`${r.url}\``);
            lines.push(`   Status: ${r.statusCode} | Latency: ${r.latencyMs}ms`);
            if (r.error) {
                lines.push(`   Error: ${r.error}`);
            }
            if (r.schemaValid !== undefined) {
                lines.push(`   Schema: ${r.schemaValid ? 'valid' : 'invalid'}`);
            }
            lines.push('');
        }
    }
    return lines.join('\n');
}
//# sourceMappingURL=index.js.map