import { and, desc, eq, gte, inArray, lt, ne, sql } from "drizzle-orm";
import { agents, approvals, budgetIncidents, budgetPolicies, organizations, costEvents, projects, } from "@rudderhq/db";
import { notFound, unprocessable } from "../errors.js";
import { logActivity } from "./activity-log.js";
function currentUtcMonthWindow(now = new Date()) {
    const year = now.getUTCFullYear();
    const month = now.getUTCMonth();
    const start = new Date(Date.UTC(year, month, 1, 0, 0, 0, 0));
    const end = new Date(Date.UTC(year, month + 1, 1, 0, 0, 0, 0));
    return { start, end };
}
function resolveWindow(windowKind, now = new Date()) {
    if (windowKind === "lifetime") {
        return {
            start: new Date(Date.UTC(1970, 0, 1, 0, 0, 0, 0)),
            end: new Date(Date.UTC(9999, 0, 1, 0, 0, 0, 0)),
        };
    }
    return currentUtcMonthWindow(now);
}
function budgetStatusFromObserved(observedAmount, amount, warnPercent) {
    if (amount <= 0)
        return "ok";
    if (observedAmount >= amount)
        return "hard_stop";
    if (observedAmount >= Math.ceil((amount * warnPercent) / 100))
        return "warning";
    return "ok";
}
function normalizeScopeName(scopeType, name) {
    if (scopeType === "organization")
        return name;
    return name.trim().length > 0 ? name : scopeType;
}
async function resolveScopeRecord(db, scopeType, scopeId) {
    if (scopeType === "organization") {
        const row = await db
            .select({
            orgId: organizations.id,
            name: organizations.name,
            status: organizations.status,
            pauseReason: organizations.pauseReason,
            pausedAt: organizations.pausedAt,
        })
            .from(organizations)
            .where(eq(organizations.id, scopeId))
            .then((rows) => rows[0] ?? null);
        if (!row)
            throw notFound("Organization not found");
        return {
            orgId: row.orgId,
            name: row.name,
            paused: row.status === "paused" || Boolean(row.pausedAt),
            pauseReason: row.pauseReason ?? null,
        };
    }
    if (scopeType === "agent") {
        const row = await db
            .select({
            orgId: agents.orgId,
            name: agents.name,
            status: agents.status,
            pauseReason: agents.pauseReason,
        })
            .from(agents)
            .where(eq(agents.id, scopeId))
            .then((rows) => rows[0] ?? null);
        if (!row)
            throw notFound("Agent not found");
        return {
            orgId: row.orgId,
            name: row.name,
            paused: row.status === "paused",
            pauseReason: row.pauseReason ?? null,
        };
    }
    const row = await db
        .select({
        orgId: projects.orgId,
        name: projects.name,
        pauseReason: projects.pauseReason,
        pausedAt: projects.pausedAt,
    })
        .from(projects)
        .where(eq(projects.id, scopeId))
        .then((rows) => rows[0] ?? null);
    if (!row)
        throw notFound("Project not found");
    return {
        orgId: row.orgId,
        name: row.name,
        paused: Boolean(row.pausedAt),
        pauseReason: row.pauseReason ?? null,
    };
}
async function computeObservedAmount(db, policy) {
    if (policy.metric !== "billed_cents")
        return 0;
    const conditions = [eq(costEvents.orgId, policy.orgId)];
    if (policy.scopeType === "agent")
        conditions.push(eq(costEvents.agentId, policy.scopeId));
    if (policy.scopeType === "project")
        conditions.push(eq(costEvents.projectId, policy.scopeId));
    const { start, end } = resolveWindow(policy.windowKind);
    if (policy.windowKind === "calendar_month_utc") {
        conditions.push(gte(costEvents.occurredAt, start));
        conditions.push(lt(costEvents.occurredAt, end));
    }
    const [row] = await db
        .select({
        total: sql `coalesce(sum(${costEvents.costCents}), 0)::int`,
    })
        .from(costEvents)
        .where(and(...conditions));
    return Number(row?.total ?? 0);
}
function buildApprovalPayload(input) {
    return {
        scopeType: input.policy.scopeType,
        scopeId: input.policy.scopeId,
        scopeName: input.scopeName,
        metric: input.policy.metric,
        windowKind: input.policy.windowKind,
        thresholdType: input.thresholdType,
        budgetAmount: input.policy.amount,
        observedAmount: input.amountObserved,
        warnPercent: input.policy.warnPercent,
        windowStart: input.windowStart.toISOString(),
        windowEnd: input.windowEnd.toISOString(),
        policyId: input.policy.id,
        guidance: "Raise the budget and resume the scope, or keep the scope paused.",
    };
}
async function markApprovalStatus(db, approvalId, status, decisionNote, decidedByUserId) {
    if (!approvalId)
        return;
    await db
        .update(approvals)
        .set({
        status,
        decisionNote: decisionNote ?? null,
        decidedByUserId,
        decidedAt: new Date(),
        updatedAt: new Date(),
    })
        .where(eq(approvals.id, approvalId));
}
export function budgetService(db, hooks = {}) {
    async function pauseScopeForBudget(policy) {
        const now = new Date();
        if (policy.scopeType === "agent") {
            await db
                .update(agents)
                .set({
                status: "paused",
                pauseReason: "budget",
                pausedAt: now,
                updatedAt: now,
            })
                .where(and(eq(agents.id, policy.scopeId), inArray(agents.status, ["active", "idle", "running", "error"])));
            return;
        }
        if (policy.scopeType === "project") {
            await db
                .update(projects)
                .set({
                pauseReason: "budget",
                pausedAt: now,
                updatedAt: now,
            })
                .where(eq(projects.id, policy.scopeId));
            return;
        }
        await db
            .update(organizations)
            .set({
            status: "paused",
            pauseReason: "budget",
            pausedAt: now,
            updatedAt: now,
        })
            .where(eq(organizations.id, policy.scopeId));
    }
    async function pauseAndCancelScopeForBudget(policy) {
        await pauseScopeForBudget(policy);
        await hooks.cancelWorkForScope?.({
            orgId: policy.orgId,
            scopeType: policy.scopeType,
            scopeId: policy.scopeId,
        });
    }
    async function resumeScopeFromBudget(policy) {
        const now = new Date();
        if (policy.scopeType === "agent") {
            await db
                .update(agents)
                .set({
                status: "idle",
                pauseReason: null,
                pausedAt: null,
                updatedAt: now,
            })
                .where(and(eq(agents.id, policy.scopeId), eq(agents.pauseReason, "budget")));
            return;
        }
        if (policy.scopeType === "project") {
            await db
                .update(projects)
                .set({
                pauseReason: null,
                pausedAt: null,
                updatedAt: now,
            })
                .where(and(eq(projects.id, policy.scopeId), eq(projects.pauseReason, "budget")));
            return;
        }
        await db
            .update(organizations)
            .set({
            status: "active",
            pauseReason: null,
            pausedAt: null,
            updatedAt: now,
        })
            .where(and(eq(organizations.id, policy.scopeId), eq(organizations.pauseReason, "budget")));
    }
    async function getPolicyRow(policyId) {
        const policy = await db
            .select()
            .from(budgetPolicies)
            .where(eq(budgetPolicies.id, policyId))
            .then((rows) => rows[0] ?? null);
        if (!policy)
            throw notFound("Budget policy not found");
        return policy;
    }
    async function listPolicyRows(orgId) {
        return db
            .select()
            .from(budgetPolicies)
            .where(eq(budgetPolicies.orgId, orgId))
            .orderBy(desc(budgetPolicies.updatedAt));
    }
    async function buildPolicySummary(policy) {
        const scope = await resolveScopeRecord(db, policy.scopeType, policy.scopeId);
        const observedAmount = await computeObservedAmount(db, policy);
        const { start, end } = resolveWindow(policy.windowKind);
        const amount = policy.isActive ? policy.amount : 0;
        const utilizationPercent = amount > 0 ? Number(((observedAmount / amount) * 100).toFixed(2)) : 0;
        return {
            policyId: policy.id,
            orgId: policy.orgId,
            scopeType: policy.scopeType,
            scopeId: policy.scopeId,
            scopeName: normalizeScopeName(policy.scopeType, scope.name),
            metric: policy.metric,
            windowKind: policy.windowKind,
            amount,
            observedAmount,
            remainingAmount: amount > 0 ? Math.max(0, amount - observedAmount) : 0,
            utilizationPercent,
            warnPercent: policy.warnPercent,
            hardStopEnabled: policy.hardStopEnabled,
            notifyEnabled: policy.notifyEnabled,
            isActive: policy.isActive,
            status: policy.isActive
                ? budgetStatusFromObserved(observedAmount, amount, policy.warnPercent)
                : "ok",
            paused: scope.paused,
            pauseReason: scope.pauseReason,
            windowStart: start,
            windowEnd: end,
        };
    }
    async function createIncidentIfNeeded(policy, thresholdType, amountObserved) {
        const { start, end } = resolveWindow(policy.windowKind);
        const existing = await db
            .select()
            .from(budgetIncidents)
            .where(and(eq(budgetIncidents.policyId, policy.id), eq(budgetIncidents.windowStart, start), eq(budgetIncidents.thresholdType, thresholdType), ne(budgetIncidents.status, "dismissed")))
            .then((rows) => rows[0] ?? null);
        if (existing)
            return existing;
        const scope = await resolveScopeRecord(db, policy.scopeType, policy.scopeId);
        const payload = buildApprovalPayload({
            policy,
            scopeName: normalizeScopeName(policy.scopeType, scope.name),
            thresholdType,
            amountObserved,
            windowStart: start,
            windowEnd: end,
        });
        const approval = thresholdType === "hard"
            ? await db
                .insert(approvals)
                .values({
                orgId: policy.orgId,
                type: "budget_override_required",
                requestedByUserId: null,
                requestedByAgentId: null,
                status: "pending",
                payload,
            })
                .returning()
                .then((rows) => rows[0] ?? null)
            : null;
        return db
            .insert(budgetIncidents)
            .values({
            orgId: policy.orgId,
            policyId: policy.id,
            scopeType: policy.scopeType,
            scopeId: policy.scopeId,
            metric: policy.metric,
            windowKind: policy.windowKind,
            windowStart: start,
            windowEnd: end,
            thresholdType,
            amountLimit: policy.amount,
            amountObserved,
            status: "open",
            approvalId: approval?.id ?? null,
        })
            .returning()
            .then((rows) => rows[0] ?? null);
    }
    async function resolveOpenSoftIncidents(policyId) {
        await db
            .update(budgetIncidents)
            .set({
            status: "resolved",
            resolvedAt: new Date(),
            updatedAt: new Date(),
        })
            .where(and(eq(budgetIncidents.policyId, policyId), eq(budgetIncidents.thresholdType, "soft"), eq(budgetIncidents.status, "open")));
    }
    async function resolveOpenIncidentsForPolicy(policyId, approvalStatus, decidedByUserId) {
        const openRows = await db
            .select()
            .from(budgetIncidents)
            .where(and(eq(budgetIncidents.policyId, policyId), eq(budgetIncidents.status, "open")));
        await db
            .update(budgetIncidents)
            .set({
            status: "resolved",
            resolvedAt: new Date(),
            updatedAt: new Date(),
        })
            .where(and(eq(budgetIncidents.policyId, policyId), eq(budgetIncidents.status, "open")));
        if (!approvalStatus || !decidedByUserId)
            return;
        for (const row of openRows) {
            await markApprovalStatus(db, row.approvalId ?? null, approvalStatus, "Resolved via budget update", decidedByUserId);
        }
    }
    async function hydrateIncidentRows(rows) {
        const approvalIds = rows.map((row) => row.approvalId).filter((value) => Boolean(value));
        const approvalRows = approvalIds.length > 0
            ? await db
                .select({ id: approvals.id, status: approvals.status })
                .from(approvals)
                .where(inArray(approvals.id, approvalIds))
            : [];
        const approvalStatusById = new Map(approvalRows.map((row) => [row.id, row.status]));
        return Promise.all(rows.map(async (row) => {
            const scope = await resolveScopeRecord(db, row.scopeType, row.scopeId);
            return {
                id: row.id,
                orgId: row.orgId,
                policyId: row.policyId,
                scopeType: row.scopeType,
                scopeId: row.scopeId,
                scopeName: normalizeScopeName(row.scopeType, scope.name),
                metric: row.metric,
                windowKind: row.windowKind,
                windowStart: row.windowStart,
                windowEnd: row.windowEnd,
                thresholdType: row.thresholdType,
                amountLimit: row.amountLimit,
                amountObserved: row.amountObserved,
                status: row.status,
                approvalId: row.approvalId ?? null,
                approvalStatus: row.approvalId ? approvalStatusById.get(row.approvalId) ?? null : null,
                resolvedAt: row.resolvedAt ?? null,
                createdAt: row.createdAt,
                updatedAt: row.updatedAt,
            };
        }));
    }
    return {
        listPolicies: async (orgId) => {
            const rows = await listPolicyRows(orgId);
            return rows.map((row) => ({
                ...row,
                scopeType: row.scopeType,
                metric: row.metric,
                windowKind: row.windowKind,
            }));
        },
        upsertPolicy: async (orgId, input, actorUserId) => {
            const scope = await resolveScopeRecord(db, input.scopeType, input.scopeId);
            if (scope.orgId !== orgId) {
                throw unprocessable("Budget scope does not belong to organization");
            }
            const metric = input.metric ?? "billed_cents";
            const windowKind = input.windowKind ?? (input.scopeType === "project" ? "lifetime" : "calendar_month_utc");
            const amount = Math.max(0, Math.floor(input.amount));
            const nextIsActive = amount > 0 && (input.isActive ?? true);
            const existing = await db
                .select()
                .from(budgetPolicies)
                .where(and(eq(budgetPolicies.orgId, orgId), eq(budgetPolicies.scopeType, input.scopeType), eq(budgetPolicies.scopeId, input.scopeId), eq(budgetPolicies.metric, metric), eq(budgetPolicies.windowKind, windowKind)))
                .then((rows) => rows[0] ?? null);
            const now = new Date();
            const row = existing
                ? await db
                    .update(budgetPolicies)
                    .set({
                    amount,
                    warnPercent: input.warnPercent ?? existing.warnPercent,
                    hardStopEnabled: input.hardStopEnabled ?? existing.hardStopEnabled,
                    notifyEnabled: input.notifyEnabled ?? existing.notifyEnabled,
                    isActive: nextIsActive,
                    updatedByUserId: actorUserId,
                    updatedAt: now,
                })
                    .where(eq(budgetPolicies.id, existing.id))
                    .returning()
                    .then((rows) => rows[0])
                : await db
                    .insert(budgetPolicies)
                    .values({
                    orgId,
                    scopeType: input.scopeType,
                    scopeId: input.scopeId,
                    metric,
                    windowKind,
                    amount,
                    warnPercent: input.warnPercent ?? 80,
                    hardStopEnabled: input.hardStopEnabled ?? true,
                    notifyEnabled: input.notifyEnabled ?? true,
                    isActive: nextIsActive,
                    createdByUserId: actorUserId,
                    updatedByUserId: actorUserId,
                })
                    .returning()
                    .then((rows) => rows[0]);
            if (input.scopeType === "organization" && windowKind === "calendar_month_utc") {
                await db
                    .update(organizations)
                    .set({
                    budgetMonthlyCents: amount,
                    updatedAt: now,
                })
                    .where(eq(organizations.id, input.scopeId));
            }
            if (input.scopeType === "agent" && windowKind === "calendar_month_utc") {
                await db
                    .update(agents)
                    .set({
                    budgetMonthlyCents: amount,
                    updatedAt: now,
                })
                    .where(eq(agents.id, input.scopeId));
            }
            if (amount > 0) {
                const observedAmount = await computeObservedAmount(db, row);
                if (observedAmount < amount) {
                    await resumeScopeFromBudget(row);
                    await resolveOpenIncidentsForPolicy(row.id, actorUserId ? "approved" : null, actorUserId);
                }
                else {
                    const softThreshold = Math.ceil((row.amount * row.warnPercent) / 100);
                    if (row.notifyEnabled && observedAmount >= softThreshold) {
                        await createIncidentIfNeeded(row, "soft", observedAmount);
                    }
                    if (row.hardStopEnabled && observedAmount >= row.amount) {
                        await resolveOpenSoftIncidents(row.id);
                        await createIncidentIfNeeded(row, "hard", observedAmount);
                        await pauseAndCancelScopeForBudget(row);
                    }
                }
            }
            else {
                await resumeScopeFromBudget(row);
                await resolveOpenIncidentsForPolicy(row.id, actorUserId ? "approved" : null, actorUserId);
            }
            await logActivity(db, {
                orgId,
                actorType: "user",
                actorId: actorUserId ?? "board",
                action: "budget.policy_upserted",
                entityType: "budget_policy",
                entityId: row.id,
                details: {
                    scopeType: row.scopeType,
                    scopeId: row.scopeId,
                    amount: row.amount,
                    windowKind: row.windowKind,
                },
            });
            return buildPolicySummary(row);
        },
        overview: async (orgId) => {
            const rows = await listPolicyRows(orgId);
            const policies = await Promise.all(rows.map((row) => buildPolicySummary(row)));
            const activeIncidentRows = await db
                .select()
                .from(budgetIncidents)
                .where(and(eq(budgetIncidents.orgId, orgId), eq(budgetIncidents.status, "open")))
                .orderBy(desc(budgetIncidents.createdAt));
            const activeIncidents = await hydrateIncidentRows(activeIncidentRows);
            return {
                orgId,
                policies,
                activeIncidents,
                pausedAgentCount: policies.filter((policy) => policy.scopeType === "agent" && policy.paused).length,
                pausedProjectCount: policies.filter((policy) => policy.scopeType === "project" && policy.paused).length,
                pendingApprovalCount: activeIncidents.filter((incident) => incident.approvalStatus === "pending").length,
            };
        },
        evaluateCostEvent: async (event) => {
            const candidatePolicies = await db
                .select()
                .from(budgetPolicies)
                .where(and(eq(budgetPolicies.orgId, event.orgId), eq(budgetPolicies.isActive, true), inArray(budgetPolicies.scopeType, ["organization", "agent", "project"])));
            const relevantPolicies = candidatePolicies.filter((policy) => {
                if (policy.scopeType === "organization")
                    return policy.scopeId === event.orgId;
                if (policy.scopeType === "agent")
                    return policy.scopeId === event.agentId;
                if (policy.scopeType === "project")
                    return Boolean(event.projectId) && policy.scopeId === event.projectId;
                return false;
            });
            for (const policy of relevantPolicies) {
                if (policy.metric !== "billed_cents" || policy.amount <= 0)
                    continue;
                const observedAmount = await computeObservedAmount(db, policy);
                const softThreshold = Math.ceil((policy.amount * policy.warnPercent) / 100);
                if (policy.notifyEnabled && observedAmount >= softThreshold) {
                    const softIncident = await createIncidentIfNeeded(policy, "soft", observedAmount);
                    if (softIncident) {
                        await logActivity(db, {
                            orgId: policy.orgId,
                            actorType: "system",
                            actorId: "budget_service",
                            action: "budget.soft_threshold_crossed",
                            entityType: "budget_incident",
                            entityId: softIncident.id,
                            details: {
                                scopeType: policy.scopeType,
                                scopeId: policy.scopeId,
                                amountObserved: observedAmount,
                                amountLimit: policy.amount,
                            },
                        });
                    }
                }
                if (policy.hardStopEnabled && observedAmount >= policy.amount) {
                    await resolveOpenSoftIncidents(policy.id);
                    const hardIncident = await createIncidentIfNeeded(policy, "hard", observedAmount);
                    await pauseAndCancelScopeForBudget(policy);
                    if (hardIncident) {
                        await logActivity(db, {
                            orgId: policy.orgId,
                            actorType: "system",
                            actorId: "budget_service",
                            action: "budget.hard_threshold_crossed",
                            entityType: "budget_incident",
                            entityId: hardIncident.id,
                            details: {
                                scopeType: policy.scopeType,
                                scopeId: policy.scopeId,
                                amountObserved: observedAmount,
                                amountLimit: policy.amount,
                                approvalId: hardIncident.approvalId ?? null,
                            },
                        });
                    }
                }
            }
        },
        getInvocationBlock: async (orgId, agentId, context) => {
            const agent = await db
                .select({
                status: agents.status,
                pauseReason: agents.pauseReason,
                orgId: agents.orgId,
                name: agents.name,
            })
                .from(agents)
                .where(eq(agents.id, agentId))
                .then((rows) => rows[0] ?? null);
            if (!agent || agent.orgId !== orgId)
                throw notFound("Agent not found");
            const organization = await db
                .select({
                status: organizations.status,
                pauseReason: organizations.pauseReason,
                name: organizations.name,
            })
                .from(organizations)
                .where(eq(organizations.id, orgId))
                .then((rows) => rows[0] ?? null);
            if (!organization)
                throw notFound("Organization not found");
            if (organization.status === "paused") {
                return {
                    scopeType: "organization",
                    scopeId: orgId,
                    scopeName: organization.name,
                    reason: organization.pauseReason === "budget"
                        ? "Organization is paused because its budget hard-stop was reached."
                        : "Organization is paused and cannot start new work.",
                };
            }
            const companyPolicy = await db
                .select()
                .from(budgetPolicies)
                .where(and(eq(budgetPolicies.orgId, orgId), eq(budgetPolicies.scopeType, "organization"), eq(budgetPolicies.scopeId, orgId), eq(budgetPolicies.isActive, true), eq(budgetPolicies.metric, "billed_cents")))
                .then((rows) => rows[0] ?? null);
            if (companyPolicy && companyPolicy.hardStopEnabled && companyPolicy.amount > 0) {
                const observed = await computeObservedAmount(db, companyPolicy);
                if (observed >= companyPolicy.amount) {
                    return {
                        scopeType: "organization",
                        scopeId: orgId,
                        scopeName: organization.name,
                        reason: "Organization cannot start new work because its budget hard-stop is exceeded.",
                    };
                }
            }
            if (agent.status === "paused" && agent.pauseReason === "budget") {
                return {
                    scopeType: "agent",
                    scopeId: agentId,
                    scopeName: agent.name,
                    reason: "Agent is paused because its budget hard-stop was reached.",
                };
            }
            const agentPolicy = await db
                .select()
                .from(budgetPolicies)
                .where(and(eq(budgetPolicies.orgId, orgId), eq(budgetPolicies.scopeType, "agent"), eq(budgetPolicies.scopeId, agentId), eq(budgetPolicies.isActive, true), eq(budgetPolicies.metric, "billed_cents")))
                .then((rows) => rows[0] ?? null);
            if (agentPolicy && agentPolicy.hardStopEnabled && agentPolicy.amount > 0) {
                const observed = await computeObservedAmount(db, agentPolicy);
                if (observed >= agentPolicy.amount) {
                    return {
                        scopeType: "agent",
                        scopeId: agentId,
                        scopeName: agent.name,
                        reason: "Agent cannot start because its budget hard-stop is still exceeded.",
                    };
                }
            }
            const candidateProjectId = context?.projectId ?? null;
            if (!candidateProjectId)
                return null;
            const project = await db
                .select({
                id: projects.id,
                name: projects.name,
                orgId: projects.orgId,
                pauseReason: projects.pauseReason,
                pausedAt: projects.pausedAt,
            })
                .from(projects)
                .where(eq(projects.id, candidateProjectId))
                .then((rows) => rows[0] ?? null);
            if (!project || project.orgId !== orgId)
                return null;
            const projectPolicy = await db
                .select()
                .from(budgetPolicies)
                .where(and(eq(budgetPolicies.orgId, orgId), eq(budgetPolicies.scopeType, "project"), eq(budgetPolicies.scopeId, project.id), eq(budgetPolicies.isActive, true), eq(budgetPolicies.metric, "billed_cents")))
                .then((rows) => rows[0] ?? null);
            if (projectPolicy && projectPolicy.hardStopEnabled && projectPolicy.amount > 0) {
                const observed = await computeObservedAmount(db, projectPolicy);
                if (observed >= projectPolicy.amount) {
                    return {
                        scopeType: "project",
                        scopeId: project.id,
                        scopeName: project.name,
                        reason: "Project cannot start work because its budget hard-stop is still exceeded.",
                    };
                }
            }
            if (!project.pausedAt || project.pauseReason !== "budget")
                return null;
            return {
                scopeType: "project",
                scopeId: project.id,
                scopeName: project.name,
                reason: "Project is paused because its budget hard-stop was reached.",
            };
        },
        resolveIncident: async (orgId, incidentId, input, actorUserId) => {
            const incident = await db
                .select()
                .from(budgetIncidents)
                .where(eq(budgetIncidents.id, incidentId))
                .then((rows) => rows[0] ?? null);
            if (!incident)
                throw notFound("Budget incident not found");
            if (incident.orgId !== orgId)
                throw notFound("Budget incident not found");
            const policy = await getPolicyRow(incident.policyId);
            if (input.action === "raise_budget_and_resume") {
                const nextAmount = Math.max(0, Math.floor(input.amount ?? 0));
                const currentObserved = await computeObservedAmount(db, policy);
                if (nextAmount <= currentObserved) {
                    throw unprocessable("New budget must exceed current observed spend");
                }
                const now = new Date();
                await db
                    .update(budgetPolicies)
                    .set({
                    amount: nextAmount,
                    isActive: true,
                    updatedByUserId: actorUserId,
                    updatedAt: now,
                })
                    .where(eq(budgetPolicies.id, policy.id));
                if (policy.scopeType === "organization" && policy.windowKind === "calendar_month_utc") {
                    await db
                        .update(organizations)
                        .set({ budgetMonthlyCents: nextAmount, updatedAt: now })
                        .where(eq(organizations.id, policy.scopeId));
                }
                if (policy.scopeType === "agent" && policy.windowKind === "calendar_month_utc") {
                    await db
                        .update(agents)
                        .set({ budgetMonthlyCents: nextAmount, updatedAt: now })
                        .where(eq(agents.id, policy.scopeId));
                }
                await resumeScopeFromBudget(policy);
                await db
                    .update(budgetIncidents)
                    .set({
                    status: "resolved",
                    resolvedAt: now,
                    updatedAt: now,
                })
                    .where(and(eq(budgetIncidents.policyId, policy.id), eq(budgetIncidents.status, "open")));
                await markApprovalStatus(db, incident.approvalId ?? null, "approved", input.decisionNote, actorUserId);
            }
            else {
                await db
                    .update(budgetIncidents)
                    .set({
                    status: "dismissed",
                    resolvedAt: new Date(),
                    updatedAt: new Date(),
                })
                    .where(eq(budgetIncidents.id, incident.id));
                await markApprovalStatus(db, incident.approvalId ?? null, "rejected", input.decisionNote, actorUserId);
            }
            await logActivity(db, {
                orgId: incident.orgId,
                actorType: "user",
                actorId: actorUserId,
                action: "budget.incident_resolved",
                entityType: "budget_incident",
                entityId: incident.id,
                details: {
                    action: input.action,
                    amount: input.amount ?? null,
                    scopeType: incident.scopeType,
                    scopeId: incident.scopeId,
                },
            });
            const [updated] = await hydrateIncidentRows([{
                    ...incident,
                    status: input.action === "raise_budget_and_resume" ? "resolved" : "dismissed",
                    resolvedAt: new Date(),
                    updatedAt: new Date(),
                }]);
            return updated;
        },
    };
}
//# sourceMappingURL=budgets.js.map