import { and, asc, eq, inArray } from "drizzle-orm";
import { approvalComments, approvals } from "@rudderhq/db";
import { notFound, unprocessable } from "../errors.js";
import { redactCurrentUserText } from "../log-redaction.js";
import { agentService } from "./agents.js";
import { budgetService } from "./budgets.js";
import { notifyHireApproved } from "./hire-hook.js";
import { instanceSettingsService } from "./instance-settings.js";
export function approvalService(db) {
    const agentsSvc = agentService(db);
    const budgets = budgetService(db);
    const instanceSettings = instanceSettingsService(db);
    const canResolveStatuses = new Set(["pending", "revision_requested"]);
    const resolvableStatuses = Array.from(canResolveStatuses);
    function redactApprovalComment(comment, censorUsernameInLogs) {
        return {
            ...comment,
            body: redactCurrentUserText(comment.body, { enabled: censorUsernameInLogs }),
        };
    }
    async function getExistingApproval(id) {
        const existing = await db
            .select()
            .from(approvals)
            .where(eq(approvals.id, id))
            .then((rows) => rows[0] ?? null);
        if (!existing)
            throw notFound("Approval not found");
        return existing;
    }
    async function resolveApproval(id, targetStatus, decidedByUserId, decisionNote, payloadOverride) {
        const existing = await getExistingApproval(id);
        if (!canResolveStatuses.has(existing.status)) {
            if (existing.status === targetStatus) {
                return { approval: existing, applied: false };
            }
            throw unprocessable(`Only pending or revision requested approvals can be ${targetStatus === "approved" ? "approved" : "rejected"}`);
        }
        const now = new Date();
        const updated = await db
            .update(approvals)
            .set({
            status: targetStatus,
            ...(payloadOverride ? { payload: payloadOverride } : {}),
            decidedByUserId,
            decisionNote: decisionNote ?? null,
            decidedAt: now,
            updatedAt: now,
        })
            .where(and(eq(approvals.id, id), inArray(approvals.status, resolvableStatuses)))
            .returning()
            .then((rows) => rows[0] ?? null);
        if (updated) {
            return { approval: updated, applied: true };
        }
        const latest = await getExistingApproval(id);
        if (latest.status === targetStatus) {
            return { approval: latest, applied: false };
        }
        throw unprocessable(`Only pending or revision requested approvals can be ${targetStatus === "approved" ? "approved" : "rejected"}`);
    }
    return {
        list: (orgId, status) => {
            const conditions = [eq(approvals.orgId, orgId)];
            if (status)
                conditions.push(eq(approvals.status, status));
            return db.select().from(approvals).where(and(...conditions));
        },
        getById: (id) => db
            .select()
            .from(approvals)
            .where(eq(approvals.id, id))
            .then((rows) => rows[0] ?? null),
        create: (orgId, data) => db
            .insert(approvals)
            .values({ ...data, orgId })
            .returning()
            .then((rows) => rows[0]),
        approve: async (id, decidedByUserId, decisionNote, payloadOverride) => {
            const { approval: updated, applied } = await resolveApproval(id, "approved", decidedByUserId, decisionNote, payloadOverride);
            let hireApprovedAgentId = null;
            const now = new Date();
            if (applied && updated.type === "hire_agent") {
                const payload = updated.payload;
                const payloadAgentId = typeof payload.agentId === "string" ? payload.agentId : null;
                if (payloadAgentId) {
                    await agentsSvc.activatePendingApproval(payloadAgentId);
                    hireApprovedAgentId = payloadAgentId;
                }
                else {
                    const created = await agentsSvc.create(updated.orgId, {
                        name: String(payload.name ?? "New Agent"),
                        role: String(payload.role ?? "general"),
                        title: typeof payload.title === "string" ? payload.title : null,
                        reportsTo: typeof payload.reportsTo === "string" ? payload.reportsTo : null,
                        capabilities: typeof payload.capabilities === "string" ? payload.capabilities : null,
                        agentRuntimeType: String(payload.agentRuntimeType ?? "process"),
                        agentRuntimeConfig: typeof payload.agentRuntimeConfig === "object" && payload.agentRuntimeConfig !== null
                            ? payload.agentRuntimeConfig
                            : {},
                        budgetMonthlyCents: typeof payload.budgetMonthlyCents === "number" ? payload.budgetMonthlyCents : 0,
                        metadata: typeof payload.metadata === "object" && payload.metadata !== null
                            ? payload.metadata
                            : null,
                        status: "idle",
                        spentMonthlyCents: 0,
                        permissions: undefined,
                        lastHeartbeatAt: null,
                    });
                    hireApprovedAgentId = created?.id ?? null;
                }
                if (hireApprovedAgentId) {
                    const budgetMonthlyCents = typeof payload.budgetMonthlyCents === "number" ? payload.budgetMonthlyCents : 0;
                    if (budgetMonthlyCents > 0) {
                        await budgets.upsertPolicy(updated.orgId, {
                            scopeType: "agent",
                            scopeId: hireApprovedAgentId,
                            amount: budgetMonthlyCents,
                            windowKind: "calendar_month_utc",
                        }, decidedByUserId);
                    }
                    void notifyHireApproved(db, {
                        orgId: updated.orgId,
                        agentId: hireApprovedAgentId,
                        source: "approval",
                        sourceId: id,
                        approvedAt: now,
                    }).catch(() => { });
                }
            }
            return { approval: updated, applied };
        },
        reject: async (id, decidedByUserId, decisionNote) => {
            const { approval: updated, applied } = await resolveApproval(id, "rejected", decidedByUserId, decisionNote);
            if (applied && updated.type === "hire_agent") {
                const payload = updated.payload;
                const payloadAgentId = typeof payload.agentId === "string" ? payload.agentId : null;
                if (payloadAgentId) {
                    await agentsSvc.terminate(payloadAgentId);
                }
            }
            return { approval: updated, applied };
        },
        requestRevision: async (id, decidedByUserId, decisionNote) => {
            const existing = await getExistingApproval(id);
            if (existing.status !== "pending") {
                throw unprocessable("Only pending approvals can request revision");
            }
            const now = new Date();
            return db
                .update(approvals)
                .set({
                status: "revision_requested",
                decidedByUserId,
                decisionNote: decisionNote ?? null,
                decidedAt: now,
                updatedAt: now,
            })
                .where(eq(approvals.id, id))
                .returning()
                .then((rows) => rows[0]);
        },
        resubmit: async (id, payload) => {
            const existing = await getExistingApproval(id);
            if (existing.status !== "revision_requested") {
                throw unprocessable("Only revision requested approvals can be resubmitted");
            }
            const now = new Date();
            return db
                .update(approvals)
                .set({
                status: "pending",
                payload: payload ?? existing.payload,
                decisionNote: null,
                decidedByUserId: null,
                decidedAt: null,
                updatedAt: now,
            })
                .where(eq(approvals.id, id))
                .returning()
                .then((rows) => rows[0]);
        },
        listComments: async (approvalId) => {
            const existing = await getExistingApproval(approvalId);
            const { censorUsernameInLogs } = await instanceSettings.getGeneral();
            return db
                .select()
                .from(approvalComments)
                .where(and(eq(approvalComments.approvalId, approvalId), eq(approvalComments.orgId, existing.orgId)))
                .orderBy(asc(approvalComments.createdAt))
                .then((comments) => comments.map((comment) => redactApprovalComment(comment, censorUsernameInLogs)));
        },
        addComment: async (approvalId, body, actor) => {
            const existing = await getExistingApproval(approvalId);
            const currentUserRedactionOptions = {
                enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
            };
            const redactedBody = redactCurrentUserText(body, currentUserRedactionOptions);
            return db
                .insert(approvalComments)
                .values({
                orgId: existing.orgId,
                approvalId,
                authorAgentId: actor.agentId ?? null,
                authorUserId: actor.userId ?? null,
                body: redactedBody,
            })
                .returning()
                .then((rows) => redactApprovalComment(rows[0], currentUserRedactionOptions.enabled));
        },
    };
}
//# sourceMappingURL=approvals.js.map