import { ProviderProfileAuthServiceError, } from "../auth/ProviderProfileAuthService.js";
import { isRecord } from "../utils/json.js";
import { authorizeWorkspace, requireLocalUiSessionForBrowserRoute, } from "./authGuards.js";
import { writeJson } from "./publicResponse.js";
import { readJsonBody } from "./requestParsing.js";
import { firstCapture, matchPath, secondCapture } from "./routeMatching.js";
export async function handleProviderAuthRoutes(context) {
    const { app, auth, request, response, url } = context;
    const providerProfileListMatch = matchPath(url.pathname, /^\/workspaces\/([^/]+)\/provider-profiles$/);
    if (request.method === "GET" && providerProfileListMatch) {
        if (!requireLocalUiSessionForBrowserRoute(response, auth, {
            request,
            routeId: "workspace.provider-profiles",
        })) {
            return true;
        }
        const workspaceId = firstCapture(providerProfileListMatch);
        if (!authorizeWorkspace(response, auth, workspaceId))
            return true;
        writeJson(response, 200, {
            profiles: await formatProviderProfileList(context, workspaceId),
        }, { surface: "workspace.provider-profiles.credential" });
        return true;
    }
    const providerProfileCredentialMatch = matchPath(url.pathname, /^\/workspaces\/([^/]+)\/provider-profiles\/([^/]+)\/credential$/);
    if (providerProfileCredentialMatch) {
        if (!requireLocalUiSessionForBrowserRoute(response, auth, {
            request,
            routeId: "workspace.provider-profiles.credential",
        })) {
            return true;
        }
        const workspaceId = firstCapture(providerProfileCredentialMatch);
        const profileKey = secondCapture(providerProfileCredentialMatch);
        if (!authorizeWorkspace(response, auth, workspaceId))
            return true;
        await handleProviderProfileCredentialRoute(context, workspaceId, profileKey, "versioned");
        return true;
    }
    const openAiApiKeyMatch = matchPath(url.pathname, /^\/workspaces\/([^/]+)\/providers\/openai\/credentials\/api-key$/);
    if (openAiApiKeyMatch) {
        const workspaceId = firstCapture(openAiApiKeyMatch);
        if (!authorizeWorkspace(response, auth, workspaceId))
            return true;
        await handleProviderProfileCredentialRoute(context, workspaceId, "openai", "legacy-openai");
        return true;
    }
    return false;
}
async function formatProviderProfileList(context, workspaceId) {
    const statuses = await context.app.providerProfileAuth.listCredentialStatuses(workspaceId);
    const statusByKey = new Map(statuses.map((status) => [status.profileKey, status]));
    return context.app.providerProfileAuth
        .listProviderProfiles()
        .map((profile) => {
        const credentialStatus = statusByKey.get(profile.profileKey);
        if (!credentialStatus) {
            throw new Error("Provider profile credential status was unavailable.");
        }
        return {
            ...profile,
            credentialStatus: formatProviderProfileCredentialStatus(credentialStatus),
        };
    });
}
async function handleProviderProfileCredentialRoute(context, workspaceId, profileKey, bodyMode) {
    const { app, request, response } = context;
    try {
        if (request.method === "GET") {
            writeJson(response, 200, {
                credential: formatProviderProfileCredentialStatus(await app.providerProfileAuth.statusApiKey(workspaceId, profileKey)),
            }, { surface: "provider-profile.credential-status" });
            return;
        }
        if (request.method === "PUT") {
            const bodyResult = readProviderProfileCredentialBody(await readJsonBody(request), bodyMode);
            if (bodyResult.kind === "error") {
                writeJson(response, bodyResult.statusCode, bodyResult.body);
                return;
            }
            app.providerProfileAuth.validateApiKey(bodyResult.apiKey);
            await app.providerProfileAuth.statusApiKey(workspaceId, profileKey);
            const mutation = await mutateProviderProfileCredential(context, async () => app.providerProfileAuth.putApiKey(workspaceId, profileKey, bodyResult.apiKey));
            writeJson(response, mutation.statusCode, mutation.body, {
                surface: "provider-profile.credential-status",
            });
            return;
        }
        if (request.method === "DELETE") {
            await app.providerProfileAuth.statusApiKey(workspaceId, profileKey);
            const mutation = await mutateProviderProfileCredential(context, async () => app.providerProfileAuth.revokeApiKey(workspaceId, profileKey));
            writeJson(response, mutation.statusCode, mutation.body, {
                surface: "provider-profile.credential-status",
            });
            return;
        }
        writeJson(response, 404, {
            error: "not_found",
            message: `${request.method} ${request.url ?? ""}`,
        });
    }
    catch (error) {
        if (writeProviderProfileCredentialError(response, error))
            return;
        throw error;
    }
}
async function mutateProviderProfileCredential(context, operation) {
    const { app } = context;
    return app.runtimeCredentialLock.runExclusive(async () => {
        const stopped = await app.runtime.stop();
        if (stopped.state === "failed") {
            return {
                statusCode: 409,
                body: {
                    error: "runtime_stop_failed",
                    message: "Active runtime could not be stopped; credential was not changed.",
                },
            };
        }
        return {
            statusCode: 200,
            body: {
                credential: formatProviderProfileCredentialStatus(await operation()),
            },
        };
    });
}
function readProviderProfileCredentialBody(body, mode) {
    if (!isRecord(body)) {
        return invalidProviderProfileCredentialBody(mode);
    }
    if (mode === "legacy-openai") {
        if (typeof body.apiKey !== "string") {
            return invalidProviderProfileCredentialBody(mode);
        }
        return { kind: "ok", apiKey: body.apiKey };
    }
    if (!isRecord(body.credential)) {
        return invalidProviderProfileCredentialBody(mode);
    }
    const credential = body.credential;
    if (typeof credential.type !== "string") {
        return invalidProviderProfileCredentialBody(mode);
    }
    if (credential.type !== "api_key") {
        return {
            kind: "error",
            statusCode: 422,
            body: {
                error: "unsupported_credential",
                message: "Only api_key credentials are supported for this profile.",
            },
        };
    }
    if (typeof credential.value !== "string") {
        return invalidProviderProfileCredentialBody(mode);
    }
    return { kind: "ok", apiKey: credential.value };
}
function invalidProviderProfileCredentialBody(mode) {
    return {
        kind: "error",
        statusCode: 400,
        body: {
            error: "invalid_request",
            message: mode === "legacy-openai"
                ? "Expected JSON body with apiKey string."
                : 'Expected JSON body with credential { type: "api_key", value: string }.',
        },
    };
}
function writeProviderProfileCredentialError(response, error) {
    if (!(error instanceof ProviderProfileAuthServiceError))
        return false;
    if (error.code === "provider_profile_not_found") {
        writeJson(response, 404, {
            error: error.code,
            message: "Provider profile is not supported.",
        });
        return true;
    }
    if (error.code === "invalid_credential") {
        writeJson(response, 422, {
            error: error.code,
            message: error.message,
        });
        return true;
    }
    writeJson(response, 422, {
        error: error.code,
        message: "Credential type is not supported for this provider profile.",
    });
    return true;
}
function formatProviderProfileCredentialStatus(status) {
    return {
        profileKey: status.profileKey,
        displayName: status.displayName,
        credential: { ...status.credential },
        runtimeExecutable: status.runtimeExecutable,
        runnable: status.runnable,
        runnableBlockerReason: status.runnableBlockerReason,
        state: status.state,
        ...(status.maskedLabel ? { maskedLabel: status.maskedLabel } : {}),
        ...(status.updatedAt ? { updatedAt: status.updatedAt } : {}),
        ...(status.backend ? { backend: status.backend } : {}),
        ...(status.degraded !== undefined ? { degraded: status.degraded } : {}),
    };
}
