/**
 * Evidence Collector
 *
 * Collects and packages audit evidence for compliance.
 *
 * @module evidence/collector
 */
import { readFile, access, stat, readdir } from "fs/promises";
import { join, basename } from "path";
import { createHash, randomUUID } from "crypto";
import { platform, release, hostname } from "os";
import { logger } from "../logger.js";
const VASPERA_DIR = ".vaspera";
const DEFAULT_MAX_INLINE_SIZE = 50 * 1024; // 50KB
/**
 * Get package.json version
 */
async function getVasperaVersion() {
    try {
        const pkgPath = join(process.cwd(), "package.json");
        const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
        return pkg.version || "unknown";
    }
    catch {
        return "unknown";
    }
}
/**
 * Detect CI environment
 */
function detectCIEnvironment() {
    // GitHub Actions
    if (process.env.GITHUB_ACTIONS === "true") {
        return {
            provider: "github",
            buildId: process.env.GITHUB_RUN_ID || "",
            commitSha: process.env.GITHUB_SHA || "",
            ref: process.env.GITHUB_REF,
            actor: process.env.GITHUB_ACTOR,
            runId: process.env.GITHUB_RUN_NUMBER,
            repository: process.env.GITHUB_REPOSITORY
                ? {
                    owner: process.env.GITHUB_REPOSITORY.split("/")[0],
                    name: process.env.GITHUB_REPOSITORY.split("/")[1],
                    url: `https://github.com/${process.env.GITHUB_REPOSITORY}`,
                }
                : undefined,
            pullRequest: process.env.GITHUB_EVENT_NAME === "pull_request"
                ? {
                    number: parseInt(process.env.GITHUB_REF?.split("/")[2] || "0", 10),
                }
                : undefined,
        };
    }
    // GitLab CI
    if (process.env.GITLAB_CI === "true") {
        return {
            provider: "gitlab",
            buildId: process.env.CI_PIPELINE_ID || "",
            commitSha: process.env.CI_COMMIT_SHA || "",
            ref: process.env.CI_COMMIT_REF_NAME,
            actor: process.env.GITLAB_USER_LOGIN,
            runId: process.env.CI_JOB_ID,
            repository: {
                owner: process.env.CI_PROJECT_NAMESPACE || "",
                name: process.env.CI_PROJECT_NAME || "",
                url: process.env.CI_PROJECT_URL || "",
            },
            pullRequest: process.env.CI_MERGE_REQUEST_IID
                ? {
                    number: parseInt(process.env.CI_MERGE_REQUEST_IID, 10),
                    title: process.env.CI_MERGE_REQUEST_TITLE,
                }
                : undefined,
        };
    }
    // Jenkins
    if (process.env.JENKINS_URL) {
        return {
            provider: "jenkins",
            buildId: process.env.BUILD_ID || "",
            commitSha: process.env.GIT_COMMIT || "",
            ref: process.env.GIT_BRANCH,
            runId: process.env.BUILD_NUMBER,
        };
    }
    // CircleCI
    if (process.env.CIRCLECI === "true") {
        return {
            provider: "circleci",
            buildId: process.env.CIRCLE_BUILD_NUM || "",
            commitSha: process.env.CIRCLE_SHA1 || "",
            ref: process.env.CIRCLE_BRANCH,
            actor: process.env.CIRCLE_USERNAME,
            repository: {
                owner: process.env.CIRCLE_PROJECT_USERNAME || "",
                name: process.env.CIRCLE_PROJECT_REPONAME || "",
                url: `https://github.com/${process.env.CIRCLE_PROJECT_USERNAME}/${process.env.CIRCLE_PROJECT_REPONAME}`,
            },
            pullRequest: process.env.CIRCLE_PULL_REQUEST
                ? {
                    number: parseInt(process.env.CIRCLE_PULL_REQUEST.split("/").pop() || "0", 10),
                    url: process.env.CIRCLE_PULL_REQUEST,
                }
                : undefined,
        };
    }
    // Generic CI detection
    if (process.env.CI === "true" || process.env.CI === "1") {
        return {
            provider: "unknown",
            buildId: process.env.BUILD_ID || process.env.BUILD_NUMBER || "",
            commitSha: process.env.COMMIT_SHA || process.env.GIT_COMMIT || "",
        };
    }
    return undefined;
}
/**
 * Capture runtime environment
 */
async function captureEnvironment() {
    const ci = detectCIEnvironment();
    return {
        os: platform(),
        osVersion: release(),
        nodeVersion: process.version,
        vasperaVersion: await getVasperaVersion(),
        hostname: ci ? undefined : hostname().slice(0, 8), // Truncate for privacy in non-CI
        ci,
        capturedAt: new Date().toISOString(),
    };
}
/**
 * Calculate SHA-256 hash of content
 */
function calculateDigest(content) {
    return createHash("sha256").update(content).digest("hex");
}
/**
 * Create an artifact from a file
 */
async function createFileArtifact(filePath, type, name, description, maxInlineSize) {
    try {
        await access(filePath);
        const stats = await stat(filePath);
        const content = await readFile(filePath, "utf-8");
        const digest = calculateDigest(content);
        const artifact = {
            type,
            name,
            description,
            contentDigest: digest,
            sizeBytes: stats.size,
            collectedAt: new Date().toISOString(),
            sourcePath: filePath,
        };
        // Inline small artifacts
        if (stats.size <= maxInlineSize) {
            artifact.content = content;
        }
        return artifact;
    }
    catch {
        return null;
    }
}
/**
 * Create an artifact from inline content
 */
function createInlineArtifact(content, type, name, description) {
    const contentBytes = Buffer.from(content, "utf-8");
    return {
        type,
        name,
        description,
        contentDigest: calculateDigest(contentBytes),
        sizeBytes: contentBytes.length,
        collectedAt: new Date().toISOString(),
        content,
    };
}
/**
 * Collect evidence artifacts
 */
export async function collectEvidence(options) {
    const { projectPath, certificationId, frameworks = [], includeSbom = true, includeHistory = true, includeScanResults = true, includeConfig = true, maxInlineSize = DEFAULT_MAX_INLINE_SIZE, } = options;
    const warnings = [];
    const artifacts = [];
    logger.info("evidence.collect.start", { projectPath, certificationId });
    try {
        const vasperaDir = join(projectPath, VASPERA_DIR);
        // Collect config files
        if (includeConfig) {
            const configFile = join(vasperaDir, "config.json");
            const configArtifact = await createFileArtifact(configFile, "config_file", "vaspera-config", "Vaspera configuration file", maxInlineSize);
            if (configArtifact) {
                artifacts.push(configArtifact);
            }
        }
        // Collect history snapshot
        if (includeHistory) {
            const historyFile = join(vasperaDir, "history.jsonl");
            const historyArtifact = await createFileArtifact(historyFile, "history_snapshot", "audit-history", "Certification and scan history with hash chain", maxInlineSize);
            if (historyArtifact) {
                artifacts.push(historyArtifact);
            }
            else {
                warnings.push("History file not found - audit trail not included");
            }
        }
        // Collect scan results
        if (includeScanResults) {
            const scansDir = join(vasperaDir, "scans");
            try {
                await access(scansDir);
                const files = await readdir(scansDir);
                const jsonFiles = files.filter((f) => f.endsWith(".json")).slice(0, 10); // Limit to 10 most recent
                for (const file of jsonFiles) {
                    const scanArtifact = await createFileArtifact(join(scansDir, file), "scan_result", `scan-${basename(file, ".json")}`, "Deterministic scanner results", maxInlineSize);
                    if (scanArtifact) {
                        artifacts.push(scanArtifact);
                    }
                }
            }
            catch {
                warnings.push("Scans directory not found");
            }
        }
        // Collect compliance reports
        if (frameworks.length > 0) {
            const reportsDir = join(vasperaDir, "reports");
            try {
                await access(reportsDir);
                const files = await readdir(reportsDir);
                for (const framework of frameworks) {
                    const reportFile = files.find((f) => f.includes(framework.toLowerCase()));
                    if (reportFile) {
                        const reportArtifact = await createFileArtifact(join(reportsDir, reportFile), "compliance_report", `compliance-${framework}`, `${framework} compliance report`, maxInlineSize);
                        if (reportArtifact) {
                            artifacts.push(reportArtifact);
                        }
                    }
                }
            }
            catch {
                warnings.push("Reports directory not found");
            }
        }
        // Collect SBOM
        if (includeSbom) {
            const sbomFile = join(vasperaDir, "sbom.json");
            const sbomArtifact = await createFileArtifact(sbomFile, "sbom", "sbom", "Software Bill of Materials (CycloneDX)", maxInlineSize);
            if (sbomArtifact) {
                artifacts.push(sbomArtifact);
            }
        }
        // Calculate bundle digest from all artifact digests
        const allDigests = artifacts.map((a) => a.contentDigest).sort().join("");
        const bundleDigest = calculateDigest(allDigests);
        // Capture environment
        const environment = await captureEnvironment();
        // Create the bundle
        const bundle = {
            id: `evidence-${randomUUID().slice(0, 12)}`,
            certificationId,
            createdAt: new Date().toISOString(),
            projectPath,
            frameworks,
            environment,
            artifacts,
            bundleDigest,
        };
        logger.info("evidence.collect.complete", {
            bundleId: bundle.id,
            artifactCount: artifacts.length,
            warningCount: warnings.length,
        });
        return {
            success: true,
            bundle,
            warnings,
        };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : "Unknown error";
        logger.error("evidence.collect.failed", { error: message });
        return {
            success: false,
            error: message,
            warnings,
        };
    }
}
/**
 * Calculate bundle digest from artifacts
 */
export function calculateBundleDigest(artifacts) {
    const allDigests = artifacts.map((a) => a.contentDigest).sort().join("");
    return calculateDigest(allDigests);
}
/**
 * Format evidence bundle as markdown summary
 */
export function formatEvidenceBundleAsMarkdown(bundle) {
    const lines = [
        "# Evidence Bundle",
        "",
        `**Bundle ID**: ${bundle.id}`,
        `**Created**: ${bundle.createdAt}`,
        `**Project**: ${bundle.projectPath}`,
        bundle.certificationId ? `**Certification**: ${bundle.certificationId}` : "",
        "",
        "## Environment",
        "",
        `| Property | Value |`,
        `|----------|-------|`,
        `| OS | ${bundle.environment.os} ${bundle.environment.osVersion} |`,
        `| Node.js | ${bundle.environment.nodeVersion} |`,
        `| Vaspera | ${bundle.environment.vasperaVersion} |`,
    ];
    if (bundle.environment.ci) {
        lines.push(`| CI Provider | ${bundle.environment.ci.provider} |`, `| Build ID | ${bundle.environment.ci.buildId} |`, `| Commit | \`${bundle.environment.ci.commitSha.slice(0, 8)}\` |`);
    }
    lines.push("", "## Artifacts", "", `| Type | Name | Size | Digest |`, `|------|------|------|--------|`);
    for (const artifact of bundle.artifacts) {
        const sizeKB = (artifact.sizeBytes / 1024).toFixed(1);
        lines.push(`| ${artifact.type} | ${artifact.name} | ${sizeKB}KB | \`${artifact.contentDigest.slice(0, 16)}...\` |`);
    }
    lines.push("", "## Bundle Integrity", "", `**Bundle Digest**: \`${bundle.bundleDigest}\``, "");
    if (bundle.signature) {
        lines.push("**Signature**: Present", bundle.signature.rekorLogIndex
            ? `**Rekor Log Index**: ${bundle.signature.rekorLogIndex}`
            : "");
    }
    else {
        lines.push("**Signature**: Not signed");
    }
    return lines.filter(Boolean).join("\n");
}
//# sourceMappingURL=collector.js.map