/**
 * Build the Deployment label selector for a service. Agents (CLINT) are
 * disambiguated by their prefix via the chart's `clint.vetra.io/agent`
 * label; CONNECT/SWITCHBOARD select by the chart's component label. There
 * is exactly one Deployment per (env, component) and per (env, agent),
 * so the caller treats >1 match as AMBIGUOUS_SERVICE.
 */
export function deploymentSelector(service, agentPrefix) {
    if (service === "CLINT") {
        if (!agentPrefix)
            throw new Error("AMBIGUOUS_SERVICE");
        return `clint.vetra.io/agent=${agentPrefix}`;
    }
    return `app.kubernetes.io/component=${service.toLowerCase()}`;
}
/**
 * `restartEnvironmentService` resolver. Owner-or-admin gated. Resolves the
 * target Deployment by label (robust to the chart's 63-char name
 * truncation) and triggers a rolling restart. The env's k8s namespace is
 * its `tenantId` (the chart deploys each environment into a namespace named
 * for its tenantId).
 *
 * Errors (plain `Error("CODE")`, matching the subgraph convention):
 * RESTART_NOT_CONFIGURED, UNAUTHENTICATED, ENV_NOT_FOUND, FORBIDDEN,
 * AMBIGUOUS_SERVICE, DEPLOYMENT_NOT_FOUND.
 */
export function createRestartResolver(deps) {
    return {
        Mutation: {
            restartEnvironmentService: async (_parent, { tenantId, service, agentPrefix, }, ctx) => {
                if (!deps)
                    throw new Error("RESTART_NOT_CONFIGURED");
                const caller = ctx.user?.address?.toLowerCase();
                if (!caller)
                    throw new Error("UNAUTHENTICATED");
                const isAdmin = ctx.isAdmin?.(caller) ?? false;
                const envRow = (await deps.envDb
                    .selectFrom("environments")
                    .select(["tenantId", "owner"])
                    .where("tenantId", "=", tenantId)
                    .executeTakeFirst());
                if (!envRow)
                    throw new Error("ENV_NOT_FOUND");
                const isOwner = !!envRow.owner && envRow.owner.toLowerCase() === caller;
                if (!isOwner && !isAdmin)
                    throw new Error("FORBIDDEN");
                // Throws AMBIGUOUS_SERVICE for CLINT without a prefix.
                const selector = deploymentSelector(service, agentPrefix);
                const names = await deps.k8s.listDeploymentNames(tenantId, selector);
                if (names.length === 0)
                    throw new Error("DEPLOYMENT_NOT_FOUND");
                if (names.length > 1)
                    throw new Error("AMBIGUOUS_SERVICE");
                const deploymentName = names[0];
                await deps.k8s.restartDeployment(tenantId, deploymentName, new Date().toISOString());
                return { ok: true, deploymentName, message: null };
            },
        },
    };
}
/**
 * Production implementation. Uses the in-cluster ServiceAccount via
 * `loadFromCluster()` (the subgraph runs in-cluster). RBAC grants
 * `apps/deployments: get, list, patch` cluster-wide.
 */
export async function createDefaultRestartK8sClient() {
    const { KubeConfig, AppsV1Api, PatchStrategy, setHeaderOptions } = await import("@kubernetes/client-node");
    const kc = new KubeConfig();
    kc.loadFromCluster();
    const apps = kc.makeApiClient(AppsV1Api);
    return {
        async listDeploymentNames(namespace, labelSelector) {
            const res = await apps.listNamespacedDeployment({
                namespace,
                labelSelector,
            });
            return res.items
                .map((d) => d.metadata?.name ?? "")
                .filter((n) => n.length > 0);
        },
        async restartDeployment(namespace, name, restartedAt) {
            // Native-resource patch defaults to JSON Patch (an array of ops) in
            // client-node, which rejects a merge object with "cannot unmarshal
            // object into []jsonPatchOp". Force a strategic-merge patch via the
            // options arg so the partial object is merged into the pod template —
            // same mechanism as `kubectl rollout restart`.
            try {
                await apps.patchNamespacedDeployment({
                    namespace,
                    name,
                    body: {
                        spec: {
                            template: {
                                metadata: {
                                    annotations: {
                                        "kubectl.kubernetes.io/restartedAt": restartedAt,
                                    },
                                },
                            },
                        },
                    },
                }, setHeaderOptions("Content-Type", PatchStrategy.StrategicMergePatch));
            }
            catch (err) {
                const code = err?.code ??
                    err?.statusCode;
                if (code === 404)
                    throw new Error("DEPLOYMENT_NOT_FOUND");
                throw err;
            }
        },
    };
}
