var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import DatabaseService from "./DatabaseService";
import Model from "../../Models/DatabaseModels/KubernetesContainer";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import logger from "../Utils/Logger";
const UPSERT_BATCH_SIZE = 500;
const STALE_DELETE_WARN_THRESHOLD = 500;
const UPSERT_COLUMNS = [
    "projectId",
    "kubernetesClusterId",
    "podNamespaceKey",
    "podName",
    "name",
    "image",
    "state",
    "reason",
    "isReady",
    "restartCount",
    "memoryLimitBytes",
    "lastSeenAt",
    "version",
];
export class Service extends DatabaseService {
    constructor() {
        super(Model);
    }
    /**
     * Upsert a batch of parsed containers for a single (project, cluster)
     * pair. Uses ON CONFLICT on the UNIQUE (projectId, clusterId,
     * podNamespaceKey, podName, name) index with a dominance guard on
     * lastSeenAt so out-of-order ingest never regresses a newer snapshot.
     *
     * Note: the upsert deliberately leaves latestCpuPercent /
     * latestMemoryBytes / metricsUpdatedAt untouched — those are owned
     * by the separate metric write path.
     */
    async bulkUpsert(data) {
        if (data.containers.length === 0) {
            return;
        }
        for (let i = 0; i < data.containers.length; i += UPSERT_BATCH_SIZE) {
            const chunk = data.containers.slice(i, i + UPSERT_BATCH_SIZE);
            const valueFragments = [];
            const params = [];
            let paramIndex = 1;
            for (const c of chunk) {
                const placeholders = [];
                for (let p = 0; p < UPSERT_COLUMNS.length; p++) {
                    placeholders.push(`$${paramIndex++}`);
                }
                valueFragments.push(`(${placeholders.join(", ")})`);
                params.push(data.projectId.toString(), data.kubernetesClusterId.toString(), c.podNamespaceKey, c.podName, c.name, c.image, c.state, c.reason, c.isReady, c.restartCount, c.memoryLimitBytes !== null && c.memoryLimitBytes !== undefined
                    ? Math.trunc(c.memoryLimitBytes).toString()
                    : null, c.lastSeenAt, 0);
            }
            const sql = `
        INSERT INTO "KubernetesContainer" (
          "projectId", "kubernetesClusterId",
          "podNamespaceKey", "podName", "name",
          "image", "state", "reason", "isReady", "restartCount",
          "memoryLimitBytes", "lastSeenAt", "version"
        )
        VALUES ${valueFragments.join(", ")}
        ON CONFLICT ("projectId", "kubernetesClusterId", "podNamespaceKey", "podName", "name")
        DO UPDATE SET
          "image" = EXCLUDED."image",
          "state" = EXCLUDED."state",
          "reason" = EXCLUDED."reason",
          "isReady" = EXCLUDED."isReady",
          "restartCount" = EXCLUDED."restartCount",
          "memoryLimitBytes" = EXCLUDED."memoryLimitBytes",
          "lastSeenAt" = EXCLUDED."lastSeenAt",
          "updatedAt" = now()
        WHERE EXCLUDED."lastSeenAt" >= "KubernetesContainer"."lastSeenAt"
      `;
            await this.getRepository().manager.query(sql, params);
        }
    }
    /**
     * Hard-delete all containers in a cluster whose last snapshot is
     * older than olderThan. Returns the number of deleted rows.
     */
    async deleteStaleForCluster(data) {
        const result = await this.getRepository().manager.query(`DELETE FROM "KubernetesContainer" WHERE "kubernetesClusterId" = $1 AND "lastSeenAt" < $2`, [data.kubernetesClusterId.toString(), data.olderThan]);
        let affected = 0;
        if (Array.isArray(result) && result.length >= 2) {
            const second = result[1];
            if (typeof second === "number") {
                affected = second;
            }
        }
        if (affected > STALE_DELETE_WARN_THRESHOLD) {
            logger.warn(`KubernetesContainer cleanup deleted ${affected} stale rows for cluster ${data.kubernetesClusterId.toString()} — larger than expected; investigate agent health.`);
        }
        return affected;
    }
    /**
     * Update latestCpuPercent/latestMemoryBytes/metricsUpdatedAt for a
     * batch of containers. Plain UPDATE — if the snapshot row doesn't
     * exist yet, the metric write is silently skipped; the next k8s
     * snapshot creates the row and the next metric flush catches up.
     *
     * The WHERE guard ensures out-of-order metric points don't regress
     * a newer observation.
     */
    async bulkUpdateLatestMetrics(data) {
        if (data.metrics.length === 0) {
            return;
        }
        /*
         * Build a single CTE-style UPDATE per chunk: VALUES table joined to
         * the live table on the natural key. Cheaper than firing one UPDATE
         * per row and atomic per chunk.
         */
        for (let i = 0; i < data.metrics.length; i += UPSERT_BATCH_SIZE) {
            const chunk = data.metrics.slice(i, i + UPSERT_BATCH_SIZE);
            const valueFragments = [];
            const params = [
                data.projectId.toString(),
                data.kubernetesClusterId.toString(),
            ];
            let paramIndex = 3;
            for (const m of chunk) {
                valueFragments.push(`($${paramIndex++}, $${paramIndex++}, $${paramIndex++}, $${paramIndex++}::numeric, $${paramIndex++}::bigint, $${paramIndex++}::timestamptz)`);
                params.push(m.podNamespaceKey, m.podName, m.name, m.cpuPercent !== null && m.cpuPercent !== undefined
                    ? m.cpuPercent
                    : null, m.memoryBytes !== null && m.memoryBytes !== undefined
                    ? Math.trunc(m.memoryBytes).toString()
                    : null, m.observedAt);
            }
            const sql = `
        UPDATE "KubernetesContainer" AS k
        SET
          "latestCpuPercent" = COALESCE(v."cpu", k."latestCpuPercent"),
          "latestMemoryBytes" = COALESCE(v."mem", k."latestMemoryBytes"),
          "metricsUpdatedAt" = v."observedAt",
          "updatedAt" = now()
        FROM (VALUES ${valueFragments.join(", ")})
          AS v("ns", "pod", "name", "cpu", "mem", "observedAt")
        WHERE
          k."projectId" = $1
          AND k."kubernetesClusterId" = $2
          AND k."podNamespaceKey" = v."ns"
          AND k."podName" = v."pod"
          AND k."name" = v."name"
          AND (k."metricsUpdatedAt" IS NULL OR v."observedAt" >= k."metricsUpdatedAt")
      `;
            await this.getRepository().manager.query(sql, params);
        }
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "bulkUpsert", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "deleteStaleForCluster", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "bulkUpdateLatestMetrics", null);
export default new Service();
//# sourceMappingURL=KubernetesContainerService.js.map