import { randomBytes } from "node:crypto";
import path from "node:path";
import { InMemoryAuditSink } from "../audit/AuditSink.js";
import { SqliteAuditSink } from "../audit/SqliteAuditSink.js";
import { CredentialVault } from "../auth/CredentialVault.js";
import { createSecureStore } from "../auth/createSecureStore.js";
import { InMemoryCredentialBroker } from "../auth/InMemoryCredentialBroker.js";
import { RuntimeCredentialInjector } from "../auth/RuntimeCredentialInjector.js";
import { AsyncLock } from "../utils/AsyncLock.js";
import { OpenAiProviderAuthService } from "../auth/OpenAiProviderAuthService.js";
import { ProviderProfileAuthService } from "../auth/ProviderProfileAuthService.js";
import { loadSyncConfig, parseBridgeMode } from "../config/bridgeConfig.js";
import { patchtrailAppDataDir } from "../config/appDataPath.js";
import { LocalUiCapabilityService } from "../capabilities/LocalUiCapabilityService.js";
import { PermissionPolicyEngine } from "../permissions/PermissionPolicyEngine.js";
import { PermissionBridgeService } from "../permissions/PermissionBridgeService.js";
import { PermissionDecisionRegistry } from "../permissions/PermissionDecisionRegistry.js";
import { createLocalStore } from "../persistence/createLocalStore.js";
import { RuntimeEventIngestor } from "../runs/RuntimeEventIngestor.js";
import { PatchTrailRunService } from "../runs/PatchTrailRunService.js";
import { HostedEvidenceSubmitter } from "../runs/HostedEvidenceSubmitter.js";
import { RunCapMonitor } from "../runs/RunCapMonitor.js";
import { RunEvidenceService } from "../runs/RunEvidenceService.js";
import { RunValidationHarvester } from "../runs/RunValidationHarvester.js";
import { NotConfiguredOpenCodeBinaryManager, } from "../runtime/opencode/OpenCodeBinaryManager.js";
import { FileSystemOpenCodeBinaryManager } from "../runtime/opencode/FileSystemOpenCodeBinaryManager.js";
import { FileSystemTufMetadataStore } from "../runtime/opencode/FileSystemTufMetadataStore.js";
import { OpenCodeRuntimeProvider } from "../runtime/opencode/OpenCodeRuntimeProvider.js";
import { OpenCodeProcessManager } from "../runtime/opencode/OpenCodeProcessManager.js";
import { TufMetadataVerifier } from "../runtime/opencode/TufMetadataVerifier.js";
import { RuntimeDiagnosticsService } from "../runtime/RuntimeDiagnosticsService.js";
import { RuntimeSmokeResultStore } from "../runtime/RuntimeSmokeResultStore.js";
import { DebugRevealService } from "../security/DebugRevealService.js";
import { GitHubForgeAuthService } from "../forge/GitHubForgeAuthService.js";
import { LocalGitHubForgeClient } from "../forge/LocalGitHubForgeClient.js";
import { RepoPathPolicy } from "../repos/RepoPathPolicy.js";
import { RepoCloneManager } from "../repos/RepoCloneManager.js";
import { RepositoryLinkService } from "../repos/RepositoryLinkService.js";
import { parseRepoAllowedRoots } from "../config/bridgeConfig.js";
import { DeviceKeyManager } from "../authority/DeviceKeyManager.js";
import { DevicePairingService } from "../runs/devicePairingService.js";
import { LoopbackRemoteCommandDispatcher } from "../runs/loopbackRemoteCommandDispatcher.js";
import { RemoteCommandExecutor, } from "../runs/remoteCommandExecutor.js";
export function createBridgeApplication(options = {}) {
    const env = options.env ?? process.env;
    const mode = parseBridgeMode(env.NODE_ENV);
    const localStore = createLocalStore({
        env,
        mode,
    });
    // Durable, append-only audit ledger in long-lived modes so credential,
    // provider, run, and permission events survive restarts and never grow the
    // process heap without bound (the README's "append-only ledger" contract).
    // Tests keep the bounded in-memory sink for isolation and speed.
    const audit = mode === "test" ? new InMemoryAuditSink() : new SqliteAuditSink(localStore);
    const secureStore = createSecureStore({
        env,
        mode,
    });
    const credentialVault = new CredentialVault(secureStore, audit);
    const credentialBroker = new InMemoryCredentialBroker(credentialVault);
    const credentialInjector = new RuntimeCredentialInjector(credentialBroker, audit);
    const providerProfileAuth = new ProviderProfileAuthService(credentialVault, credentialBroker, audit);
    const openAiProviderAuth = new OpenAiProviderAuthService(credentialVault, credentialBroker);
    const debugReveal = new DebugRevealService(localStore, audit);
    const permissionPolicy = new PermissionPolicyEngine(audit);
    const bridgeToken = env.PATCHTRAIL_BRIDGE_CAPABILITY_TOKEN ??
        randomBytes(32).toString("base64url");
    const localUiCapabilities = new LocalUiCapabilityService(audit);
    const binaryManager = createBinaryManager(env);
    const processManager = new OpenCodeProcessManager({
        binaryManager,
        extraEnv: createOpenCodeProcessEnv(env),
        runtimeHomeDir: path.join(patchtrailAppDataDir(env), "runtime", "opencode-home"),
    });
    const runtime = new OpenCodeRuntimeProvider(binaryManager, processManager);
    const smokeResultStore = RuntimeSmokeResultStore.fromEnv(env);
    const runtimeDiagnostics = new RuntimeDiagnosticsService(runtime, providerProfileAuth, smokeResultStore);
    const permissionDecisionRegistry = new PermissionDecisionRegistry(localStore, permissionPolicy, audit);
    permissionDecisionRegistry.hydrateActiveDecisions();
    const permissionBridge = new PermissionBridgeService(localStore, permissionPolicy, runtime, audit, permissionDecisionRegistry);
    const runtimeEventIngestor = new RuntimeEventIngestor(runtime, localStore, audit, permissionBridge);
    const runtimeCredentialLock = new AsyncLock();
    const runService = new PatchTrailRunService(localStore, runtime, audit, runtimeEventIngestor, providerProfileAuth, credentialInjector, runtimeCredentialLock);
    const runValidationHarvester = new RunValidationHarvester(localStore);
    const runEvidenceService = new RunEvidenceService(localStore, runValidationHarvester);
    const hostedEvidenceSubmitter = HostedEvidenceSubmitter.fromEnv(env, {
        audit,
    });
    const runCapMonitor = new RunCapMonitor(localStore, runEvidenceService, hostedEvidenceSubmitter, audit);
    const githubForgeAuth = new GitHubForgeAuthService({
        vault: credentialVault,
        broker: credentialBroker,
        audit,
        ...(env.PATCHTRAIL_GITHUB_APP_CLIENT_ID
            ? { oauthClientId: env.PATCHTRAIL_GITHUB_APP_CLIENT_ID }
            : {}),
        ...(env.PATCHTRAIL_GITHUB_APP_CLIENT_SECRET
            ? { oauthClientSecret: env.PATCHTRAIL_GITHUB_APP_CLIENT_SECRET }
            : {}),
        ...(env.PATCHTRAIL_GITHUB_APP_SLUG
            ? { oauthAppSlug: env.PATCHTRAIL_GITHUB_APP_SLUG }
            : {}),
    });
    const githubForgeClient = new LocalGitHubForgeClient({
        broker: credentialBroker,
        forgeRef: githubForgeAuth.forgeRef(),
    });
    // Repository linking + locate/clone. The clone path is bounded to an
    // operator-configured allow-list of roots (empty by default → fail closed).
    // For a private https clone the forge token is acquired transiently from the
    // broker and byte-wiped immediately after the value is read.
    const repoPathPolicy = new RepoPathPolicy(parseRepoAllowedRoots(env.PATCHTRAIL_REPO_ALLOWED_ROOTS));
    const repoCloneManager = new RepoCloneManager();
    const repositoryLink = new RepositoryLinkService({
        store: localStore,
        audit,
        pathPolicy: repoPathPolicy,
        cloneManager: repoCloneManager,
        resolveForgeToken: async () => {
            const forgeRef = githubForgeAuth.forgeRef();
            let credential;
            try {
                credential = await credentialBroker.acquire(forgeRef);
            }
            catch {
                return null;
            }
            try {
                return new TextDecoder().decode(credential.secret);
            }
            finally {
                credentialBroker.release(forgeRef);
            }
        },
    });
    const seedStatusHolder = {
        current: null,
        set(result) {
            seedStatusHolder.current = result;
        },
    };
    // Layer 3 device pairing: the device-identity keypair lives in the secure
    // store; the pairing service claims a code against the hosted backend (the
    // bridge is always the outbound party) and records the binding locally.
    const syncConfig = loadSyncConfig(env, mode);
    const deviceKeyManager = new DeviceKeyManager(secureStore);
    const devicePairingService = new DevicePairingService({
        keyManager: deviceKeyManager,
        bindingStore: localStore,
        endpointOrigin: typeof syncConfig.endpointUrl === "string"
            ? new URL(syncConfig.endpointUrl).origin
            : "",
    });
    // Layer 3 remote-command executor. Dispatches verified+authorized commands to
    // the bridge's own existing secured loopback routes (no new write path). The
    // loopback origin uses the bridge's bound port; tests inject a fake dispatcher.
    const bridgePort = Number(env.PATCHTRAIL_BRIDGE_PORT) || 47_839;
    const loopbackOrigin = `http://127.0.0.1:${bridgePort}`;
    const remoteCommandExecutor = new RemoteCommandExecutor({
        store: localStore,
        audit,
        dispatcher: options.remoteCommandDispatcher ??
            new LoopbackRemoteCommandDispatcher({
                baseUrl: loopbackOrigin,
                bridgeToken,
                mintSession: async ({ workspaceId, deviceId, scopes }) => {
                    const minted = localUiCapabilities.mint({
                        origin: loopbackOrigin,
                        workspaceId,
                        deviceId,
                        userSessionId: "remote-command-executor",
                        scopes: scopes,
                    });
                    const exchanged = localUiCapabilities.exchange({
                        capabilityToken: minted.token,
                        origin: loopbackOrigin,
                        workspaceId,
                        deviceId,
                        userSessionId: "remote-command-executor",
                    });
                    return {
                        sessionToken: exchanged.sessionToken,
                        origin: loopbackOrigin,
                    };
                },
            }),
    });
    return {
        audit,
        secureStore,
        credentialVault,
        credentialBroker,
        credentialInjector,
        providerProfileAuth,
        openAiProviderAuth,
        localStore,
        debugReveal,
        permissionPolicy,
        bridgeToken,
        localUiCapabilities,
        binaryManager,
        processManager,
        runtime,
        smokeResultStore,
        runtimeDiagnostics,
        permissionDecisionRegistry,
        runtimeCredentialLock,
        permissionBridge,
        runtimeEventIngestor,
        runValidationHarvester,
        runService,
        runEvidenceService,
        hostedEvidenceSubmitter,
        runCapMonitor,
        githubForgeAuth,
        githubForgeClient,
        repoPathPolicy,
        repositoryLink,
        seedStatus: seedStatusHolder,
        syncConfig,
        deviceKeyManager,
        devicePairingService,
        remoteCommandExecutor,
    };
}
function createOpenCodeProcessEnv(env) {
    return {
        OPENCODE_SERVER_PASSWORD: env.PATCHTRAIL_OPENCODE_SERVER_PASSWORD ??
            randomBytes(32).toString("base64url"),
    };
}
function createBinaryManager(env) {
    const metadataDir = env.PATCHTRAIL_OPENCODE_METADATA_DIR;
    if (!metadataDir)
        return new NotConfiguredOpenCodeBinaryManager();
    const trustedRootKeyIds = splitCsv(env.PATCHTRAIL_OPENCODE_TRUSTED_ROOT_KEY_IDS);
    const artifactRoot = env.PATCHTRAIL_OPENCODE_ARTIFACT_ROOT ?? metadataDir;
    return new FileSystemOpenCodeBinaryManager({
        metadataStore: new FileSystemTufMetadataStore(metadataDir),
        verifier: new TufMetadataVerifier({ trustedRootKeyIds }),
        artifactRoot,
    });
}
function splitCsv(value) {
    if (!value)
        return [];
    return value
        .split(",")
        .map((item) => item.trim())
        .filter(Boolean);
}
