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/DockerResource";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import OneUptimeDate from "../../Types/Date";
import logger from "../Utils/Logger";
const UPSERT_BATCH_SIZE = 500;
const STALE_DELETE_WARN_THRESHOLD = 100;
export class Service extends DatabaseService {
    constructor() {
        super(Model);
    }
    /**
     * Upsert a batch of Container rows for a single (project, host)
     * pair. Uses ON CONFLICT on the UNIQUE (projectId, dockerHostId,
     * kind, name) index with a dominance guard on lastSeenAt so
     * out-of-order ingest never regresses a newer observation.
     *
     * Containers are upserted from metric snapshots (no separate
     * inventory snapshot path), so this also writes
     * latestCpuPercent / latestMemoryBytes / metricsUpdatedAt in the
     * same statement — saves a round trip vs. the K8s pattern of
     * upsert-then-update.
     */
    async bulkUpsertContainers(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 p = 1;
            for (const c of chunk) {
                valueFragments.push(`($${p++}, $${p++}, $${p++}, $${p++}, $${p++}, $${p++}, $${p++}, $${p++}::numeric, $${p++}::bigint, $${p++}::timestamptz, $${p++}::timestamptz, $${p++})`);
                params.push(data.projectId.toString(), data.dockerHostId.toString(), "Container", c.containerName, c.containerId, c.imageName, c.state, c.cpuPercent !== null && c.cpuPercent !== undefined
                    ? c.cpuPercent
                    : null, c.memoryBytes !== null && c.memoryBytes !== undefined
                    ? Math.trunc(c.memoryBytes).toString()
                    : null, c.observedAt, c.observedAt, 0);
            }
            const sql = `
        INSERT INTO "DockerResource" (
          "projectId", "dockerHostId", "kind", "name",
          "containerId", "imageName", "state",
          "latestCpuPercent", "latestMemoryBytes",
          "metricsUpdatedAt", "lastSeenAt", "version"
        )
        VALUES ${valueFragments.join(", ")}
        ON CONFLICT ("projectId", "dockerHostId", "kind", "name")
        DO UPDATE SET
          "containerId" = COALESCE(EXCLUDED."containerId", "DockerResource"."containerId"),
          "imageName" = COALESCE(EXCLUDED."imageName", "DockerResource"."imageName"),
          "state" = EXCLUDED."state",
          "latestCpuPercent" = COALESCE(EXCLUDED."latestCpuPercent", "DockerResource"."latestCpuPercent"),
          "latestMemoryBytes" = COALESCE(EXCLUDED."latestMemoryBytes", "DockerResource"."latestMemoryBytes"),
          "metricsUpdatedAt" = EXCLUDED."metricsUpdatedAt",
          "lastSeenAt" = EXCLUDED."lastSeenAt",
          "updatedAt" = now()
        WHERE EXCLUDED."lastSeenAt" >= "DockerResource"."lastSeenAt"
      `;
            await this.getRepository().manager.query(sql, params);
        }
    }
    /**
     * Upsert a batch of resources for any kind. Used by the snapshot
     * ingest path (Container / Image / Network / Volume rows from the
     * Docker agent's inventory poller). Container rows from this path
     * carry full state (running / exited / paused / restarting / dead /
     * created), unlike the metric-derived path which only sees running
     * containers.
     */
    async bulkUpsert(data) {
        if (data.resources.length === 0) {
            return;
        }
        for (let i = 0; i < data.resources.length; i += UPSERT_BATCH_SIZE) {
            const chunk = data.resources.slice(i, i + UPSERT_BATCH_SIZE);
            const valueFragments = [];
            const params = [];
            let p = 1;
            for (const r of chunk) {
                valueFragments.push(`($${p++}, $${p++}, $${p++}, $${p++}, $${p++}, $${p++}, $${p++}, $${p++}, $${p++}::timestamptz, $${p++}::timestamptz, $${p++})`);
                params.push(data.projectId.toString(), data.dockerHostId.toString(), r.kind, r.name, r.containerId, r.imageName, r.state, r.labels ? JSON.stringify(r.labels) : null, r.resourceCreationTimestamp, r.lastSeenAt, 0);
            }
            const sql = `
        INSERT INTO "DockerResource" (
          "projectId", "dockerHostId", "kind", "name",
          "containerId", "imageName", "state", "labels",
          "resourceCreationTimestamp", "lastSeenAt", "version"
        )
        VALUES ${valueFragments.join(", ")}
        ON CONFLICT ("projectId", "dockerHostId", "kind", "name")
        DO UPDATE SET
          "containerId" = COALESCE(EXCLUDED."containerId", "DockerResource"."containerId"),
          "imageName" = COALESCE(EXCLUDED."imageName", "DockerResource"."imageName"),
          "state" = COALESCE(EXCLUDED."state", "DockerResource"."state"),
          "labels" = COALESCE(EXCLUDED."labels", "DockerResource"."labels"),
          "resourceCreationTimestamp" = COALESCE(EXCLUDED."resourceCreationTimestamp", "DockerResource"."resourceCreationTimestamp"),
          "lastSeenAt" = EXCLUDED."lastSeenAt",
          "updatedAt" = now()
        WHERE EXCLUDED."lastSeenAt" >= "DockerResource"."lastSeenAt"
      `;
            await this.getRepository().manager.query(sql, params);
        }
    }
    /**
     * Hard-delete all resources on a host whose last observation is
     * older than olderThan. Returns the number of deleted rows.
     */
    async deleteStaleForHost(data) {
        const result = await this.getRepository().manager.query(`DELETE FROM "DockerResource" WHERE "dockerHostId" = $1 AND "lastSeenAt" < $2`, [data.dockerHostId.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(`DockerResource cleanup deleted ${affected} stale rows for host ${data.dockerHostId.toString()} — larger than expected; investigate agent health.`);
        }
        return affected;
    }
    /**
     * Compute container state breakdowns for a single host from the
     * inventory table. Used by the cleanup worker to refresh the cached
     * counts on DockerHost so the Hosts page / dashboard widget shows
     * accurate numbers without needing a SQL aggregation per render.
     */
    async getContainerCountsForHost(data) {
        const rows = await this.getRepository().manager.query(`SELECT
         COUNT(*) FILTER (WHERE LOWER("state") = 'running')::text AS "running",
         COUNT(*) FILTER (WHERE LOWER("state") IN ('exited', 'dead', 'created'))::text AS "stopped",
         COUNT(*) FILTER (WHERE LOWER("state") = 'paused')::text AS "paused"
       FROM "DockerResource"
       WHERE "projectId" = $1
         AND "dockerHostId" = $2
         AND "kind" = 'Container'
         AND "deletedAt" IS NULL`, [data.projectId.toString(), data.dockerHostId.toString()]);
        const row = rows[0];
        return {
            containersRunning: row ? parseInt(row.running, 10) || 0 : 0,
            containersStopped: row ? parseInt(row.stopped, 10) || 0 : 0,
            containersPaused: row ? parseInt(row.paused, 10) || 0 : 0,
        };
    }
    getStaleThresholdDate(nowOverride) {
        const minutes = this.getStaleThresholdMinutes();
        return OneUptimeDate.addRemoveMinutes(nowOverride || OneUptimeDate.getCurrentDate(), -minutes);
    }
    getStaleThresholdMinutes() {
        const raw = process.env["DOCKER_INVENTORY_STALE_MINUTES"];
        if (raw) {
            const parsed = parseInt(raw, 10);
            if (!isNaN(parsed) && parsed >= 5) {
                return parsed;
            }
        }
        return 15;
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "bulkUpsertContainers", null);
__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, "deleteStaleForHost", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getContainerCountsForHost", null);
export default new Service();
//# sourceMappingURL=DockerResourceService.js.map