import { setTimeout as sleep } from "node:timers/promises";
import { createBridgeApplication } from "../application/createApplication.js";
import { HOSTED_EVIDENCE_API_BASE_URL_ENV, } from "../runs/HostedEvidenceSubmitter.js";
import { OPENAI_SMOKE_SECRET_PATTERNS, findSmokeLeaks, nonEmptyEnv, readFlag, sanitizeSmokeError, writeSmokeJson, } from "./smoke-provider-shared.js";
export const OPENAI_PROVIDER_SMOKE_REQUIRED_SETTINGS = [
    "PATCHTRAIL_SMOKE_OPENAI",
    "PATCHTRAIL_SMOKE_OPENAI_API_KEY",
    HOSTED_EVIDENCE_API_BASE_URL_ENV,
    "PATCHTRAIL_SMOKE_RUN_INTENT_RECEIPT_JSON",
    "PATCHTRAIL_SMOKE_WORKSPACE_ROOT",
    "PATCHTRAIL_SMOKE_REPOSITORY_ID",
    "PATCHTRAIL_SMOKE_PREPARATION_ID",
    "PATCHTRAIL_SMOKE_BRANCH_PLAN_ID",
    "PATCHTRAIL_SMOKE_BRANCH_NAME",
    "PATCHTRAIL_SMOKE_BASE_BRANCH",
];
const DEFAULT_TIMEOUT_MS = 60_000;
const DEFAULT_MAX_PROMPT_TOKENS = 256;
const DEFAULT_MAX_COST_USD = 0.05;
const DEFAULT_PROMPT = "Reply with one short sentence confirming PatchTrail OpenAI BYOK smoke readiness.";
export async function main(argv = process.argv.slice(2)) {
    const result = await runOpenAiProviderSmoke(argv);
    if (result.status === "failed")
        process.exitCode = 1;
}
export async function runOpenAiProviderSmoke(argv = [], options = {}) {
    const env = options.env ?? process.env;
    const stdout = options.stdout ?? ((line) => process.stdout.write(line));
    const stderr = options.stderr ?? ((line) => process.stderr.write(line));
    const args = parseOpenAiProviderSmokeArgs(argv);
    const settings = collectOpenAiProviderSmokeSettings(env);
    if (settings.status === "environment-gated") {
        const result = {
            status: "environment-gated",
            reasonCode: "openai-smoke-environment-gated",
            missingSettings: settings.missingSettings,
        };
        stdout(`environment-gated: missing ${settings.missingSettings.join(", ")}\n`);
        if (args.json)
            writeSmokeJson(result, stdout);
        return result;
    }
    const executor = options.executor ?? executeLiveOpenAiProviderSmoke;
    const input = {
        ...settings,
        env,
        prompt: args.prompt,
        caps: {
            timeoutMs: args.timeoutMs,
            maxPromptTokens: DEFAULT_MAX_PROMPT_TOKENS,
            maxCostUsd: DEFAULT_MAX_COST_USD,
        },
    };
    let output;
    try {
        output = await withTimeout(executor(input), args.timeoutMs, "openai-smoke-timeout");
    }
    catch (error) {
        const result = error instanceof OpenAiProviderSmokeTimeoutError
            ? { status: "failed", reasonCode: error.reasonCode }
            : {
                status: "failed",
                reasonCode: "openai-smoke-execution-failed",
                diagnostic: sanitizeSmokeError(error),
            };
        stderr(`${result.reasonCode}\n`);
        if (args.json)
            writeSmokeJson(result, stdout);
        return result;
    }
    const findings = findOpenAiProviderSmokeLeaks(output.observedSurfaces ?? [], settings.apiKey);
    if (findings.length > 0) {
        const result = {
            status: "failed",
            reasonCode: "secret-leak-detected",
            findings,
        };
        stderr("secret-leak-detected\n");
        if (args.json)
            writeSmokeJson(result, stdout);
        return result;
    }
    if (output.status === "failed") {
        const result = {
            status: "failed",
            reasonCode: output.reasonCode,
        };
        stderr(`${result.reasonCode}\n`);
        if (args.json)
            writeSmokeJson(result, stdout);
        return result;
    }
    const result = {
        status: "passed",
        proof: {
            provider: "openai",
            runtime: { ...output.runtime },
            validations: output.validations.map(sanitizeValidationOutcome),
            evidence: { ...output.evidence },
            caps: input.caps,
            leakScan: { status: "passed" },
        },
    };
    stdout("passed: openai-provider-smoke\n");
    if (args.json)
        writeSmokeJson(result, stdout);
    return result;
}
export function findOpenAiProviderSmokeLeaks(value, configuredApiKey) {
    return findSmokeLeaks(value, OPENAI_SMOKE_SECRET_PATTERNS, configuredApiKey ? [configuredApiKey] : []);
}
function collectOpenAiProviderSmokeSettings(env) {
    if (!readFlag(env.PATCHTRAIL_SMOKE_OPENAI)) {
        return {
            status: "environment-gated",
            missingSettings: ["PATCHTRAIL_SMOKE_OPENAI"],
        };
    }
    const missingSettings = OPENAI_PROVIDER_SMOKE_REQUIRED_SETTINGS.filter((setting) => setting !== "PATCHTRAIL_SMOKE_OPENAI" && !nonEmptyEnv(env, setting));
    if (missingSettings.length > 0) {
        return { status: "environment-gated", missingSettings };
    }
    const receipt = readRunIntentReceiptJson(nonEmptyEnv(env, "PATCHTRAIL_SMOKE_RUN_INTENT_RECEIPT_JSON") ?? "");
    if (!receipt) {
        return {
            status: "environment-gated",
            missingSettings: ["PATCHTRAIL_SMOKE_RUN_INTENT_RECEIPT_JSON"],
        };
    }
    return {
        status: "ready",
        apiKey: nonEmptyEnv(env, "PATCHTRAIL_SMOKE_OPENAI_API_KEY") ?? "",
        hostedApiBaseUrl: nonEmptyEnv(env, HOSTED_EVIDENCE_API_BASE_URL_ENV) ?? "",
        runIntentReceipt: receipt,
        workspaceRoot: nonEmptyEnv(env, "PATCHTRAIL_SMOKE_WORKSPACE_ROOT") ?? "",
        repositoryId: nonEmptyEnv(env, "PATCHTRAIL_SMOKE_REPOSITORY_ID") ?? "",
        preparationId: nonEmptyEnv(env, "PATCHTRAIL_SMOKE_PREPARATION_ID") ?? "",
        branchPlanId: nonEmptyEnv(env, "PATCHTRAIL_SMOKE_BRANCH_PLAN_ID") ?? "",
        branchName: nonEmptyEnv(env, "PATCHTRAIL_SMOKE_BRANCH_NAME") ?? "",
        baseBranch: nonEmptyEnv(env, "PATCHTRAIL_SMOKE_BASE_BRANCH") ?? "",
    };
}
async function executeLiveOpenAiProviderSmoke(input) {
    const app = createBridgeApplication({
        env: {
            ...input.env,
            NODE_ENV: "test",
            PATCHTRAIL_CREDENTIAL_STORE: "in-memory",
            PATCHTRAIL_HOSTED_API_BASE_URL: input.hostedApiBaseUrl,
        },
    });
    try {
        await app.providerProfileAuth.putApiKey(input.runIntentReceipt.workspaceId, "openai", input.apiKey);
        const run = await app.runService.createRun({
            workspaceId: input.runIntentReceipt.workspaceId,
            rootPath: input.workspaceRoot,
            title: "OpenAI BYOK smoke",
            sourceBinding: {
                workItemId: input.runIntentReceipt.workItemId,
                repositoryId: input.repositoryId,
                preparationId: input.preparationId,
                runIntentId: input.runIntentReceipt.intentId,
                branchPlanId: input.branchPlanId,
                branchName: input.branchName,
                baseBranch: input.baseBranch,
            },
        });
        await app.runService.sendPrompt({
            runId: run.run.id,
            text: input.prompt,
            prompt: { noReply: false },
        });
        await sleep(250);
        const submission = app.runEvidenceService.captureBackendSubmission({
            runId: run.run.id,
            warnings: [],
        });
        const submitted = await app.hostedEvidenceSubmitter.submit({
            submission,
            runIntentBridgeReceipt: input.runIntentReceipt,
        });
        if (submitted.status !== "succeeded") {
            return {
                status: "failed",
                reasonCode: reasonCodeForHostedSubmission(submitted),
                observedSurfaces: [
                    { label: "hosted-evidence-submission", value: submitted },
                ],
            };
        }
        return {
            status: "passed",
            runtime: {
                provider: "opencode",
                credentialInjection: "openai-byok",
                promptAccepted: true,
            },
            validations: app.runValidationHarvester.harvest(run.run.id),
            evidence: {
                status: "submitted",
                attempts: submitted.attempts,
                evidenceId: submitted.response.evidenceId,
                patchFileCount: submitted.response.patchArtifact.fileCount,
            },
            observedSurfaces: [
                { label: "runtime-status", value: await app.runtime.status() },
                { label: "submitted-evidence", value: submitted.response },
                { label: "audit", value: app.audit.list() },
            ],
        };
    }
    finally {
        await app.runtimeEventIngestor.stop();
        await app.runtime.stop();
    }
}
function reasonCodeForHostedSubmission(submission) {
    if (submission.status === "environment-gated") {
        return "hosted-evidence-submission-failed";
    }
    return "hosted-evidence-submission-failed";
}
function parseOpenAiProviderSmokeArgs(argv) {
    const parsed = {
        json: false,
        timeoutMs: DEFAULT_TIMEOUT_MS,
        prompt: DEFAULT_PROMPT,
    };
    for (let index = 0; index < argv.length; index += 1) {
        const arg = argv[index];
        if (arg === "--")
            continue;
        if (arg === "--json") {
            parsed.json = true;
        }
        else if (arg === "--timeout-ms") {
            parsed.timeoutMs = parseTimeoutMs(requireArg(argv, ++index, arg));
        }
        else if (arg === "--prompt") {
            parsed.prompt = sanitizePrompt(requireArg(argv, ++index, arg));
        }
        else {
            throw new Error(`Unknown smoke argument: ${arg}`);
        }
    }
    return parsed;
}
function requireArg(argv, index, arg) {
    const value = argv[index];
    if (!value)
        throw new Error(`${arg} requires a value.`);
    return value;
}
function parseTimeoutMs(value) {
    const timeout = Number.parseInt(value, 10);
    if (!Number.isFinite(timeout) || timeout < 1_000 || timeout > 300_000) {
        throw new Error("--timeout-ms must be between 1000 and 300000.");
    }
    return timeout;
}
function sanitizePrompt(value) {
    const prompt = value.trim();
    if (!prompt || prompt.length > 1_000) {
        throw new Error("--prompt must be a non-empty string under 1000 characters.");
    }
    return prompt;
}
function readRunIntentReceiptJson(value) {
    let parsed;
    try {
        parsed = JSON.parse(value);
    }
    catch {
        return undefined;
    }
    if (!isRecord(parsed))
        return undefined;
    const receipt = parsed;
    const required = [
        "contractVersion",
        "receiptId",
        "keyId",
        "issuer",
        "audience",
        "intentId",
        "workspaceId",
        "workItemId",
        "status",
        "nextStepCode",
        "reasonCode",
        "issuedAt",
        "expiresAt",
        "signature",
    ];
    if (required.some((key) => typeof receipt[key] !== "string")) {
        return undefined;
    }
    return Object.fromEntries(required.map((key) => [key, receipt[key]]));
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function sanitizeValidationOutcome(outcome) {
    return {
        kind: outcome.kind,
        command: safeCommandLabel(outcome.command, outcome.kind),
        verdict: outcome.verdict,
        durationMs: Math.max(0, Math.trunc(outcome.durationMs)),
        reasonCode: outcome.reasonCode,
        ...(typeof outcome.exitCode === "number"
            ? { exitCode: Math.trunc(outcome.exitCode) }
            : {}),
    };
}
function safeCommandLabel(command, fallback) {
    const value = command.trim();
    return /^[A-Za-z0-9][A-Za-z0-9:_-]{0,79}$/u.test(value) ? value : fallback;
}
async function withTimeout(promise, timeoutMs, reasonCode) {
    let timer;
    const timeout = new Promise((_, reject) => {
        timer = setTimeout(() => reject(new OpenAiProviderSmokeTimeoutError(reasonCode)), timeoutMs);
    });
    try {
        return await Promise.race([promise, timeout]);
    }
    finally {
        if (timer)
            clearTimeout(timer);
    }
}
class OpenAiProviderSmokeTimeoutError extends Error {
    reasonCode;
    constructor(reasonCode) {
        super(reasonCode);
        this.reasonCode = reasonCode;
        this.name = "OpenAiProviderSmokeTimeoutError";
    }
}
