/**
 * Fire-and-forget telemetry + admin query facade (FR-011)
 */
import { AsyncLocalStorage } from 'async_hooks';
import { randomUUID } from 'crypto';
import { setImmediate } from 'timers';
import { TelemetryRepository } from '../repositories/telemetry-repository.js';
import { logger } from '../../../shared/utils/logger.js';
export class TelemetryService {
    repository;
    getScheduler;
    als = new AsyncLocalStorage();
    /** 행 단위 손상 JSON이 전체 목록 API를 깨지 않게 한다 (FR-010) */
    static safeParseExtraData(raw) {
        if (raw == null || raw === '')
            return null;
        try {
            return JSON.parse(raw);
        }
        catch {
            logger.warn('telemetry extra_data JSON parse failed; returning null for this row');
            return null;
        }
    }
    constructor(repository, getScheduler) {
        this.repository = repository;
        this.getScheduler = getScheduler;
    }
    getContext() {
        return this.als.getStore();
    }
    async runWithContext(ownerId, fn) {
        const requestId = randomUUID();
        return this.als.run({ requestId, ownerId }, fn);
    }
    record(partial) {
        const store = this.als.getStore();
        const requestId = partial.requestId ?? store?.requestId ?? randomUUID();
        const ownerId = partial.ownerId !== undefined ? partial.ownerId : (store?.ownerId ?? null);
        const event = {
            eventType: partial.eventType,
            requestId,
            ownerId,
            latencyMs: partial.latencyMs,
            outcome: partial.outcome,
            errorCode: partial.errorCode,
            extraData: partial.extraData
        };
        setImmediate(() => {
            try {
                this.repository.insertEventSync(event);
            }
            catch (err) {
                logger.warn('telemetry write failed', {
                    error: err instanceof Error ? err.message : String(err)
                });
            }
        });
    }
    getSearchQuality(period, ownerId) {
        return this.repository.querySearchQuality(period, ownerId);
    }
    getMemoryQuality(ownerId) {
        return this.repository.queryMemoryQuality(ownerId);
    }
    getConsolidationQuality(ownerId) {
        return this.repository.queryConsolidationQuality(ownerId);
    }
    getSystemMetrics(period, ownerId) {
        const sched = this.getScheduler?.() ?? null;
        const sleepMeta = this.buildJobSnapshot(sched, 'sleep_consolidation_batch');
        const telMeta = this.buildJobSnapshot(sched, 'telemetry_cleanup_batch');
        return this.repository.querySystemMetrics(period, ownerId, {
            sleep: sleepMeta,
            telemetryCleanup: telMeta
        });
    }
    static jobNameToTelemetryEventType(jobName) {
        if (jobName === 'sleep_consolidation_batch')
            return 'consolidation.performed';
        if (jobName === 'telemetry_cleanup_batch')
            return 'telemetry.cleanup.performed';
        return null;
    }
    buildJobSnapshot(sched, jobName) {
        const et = TelemetryService.jobNameToTelemetryEventType(jobName);
        const roll24 = et != null ? this.repository.getBackgroundJobRolling24hStats(et) : null;
        if (!sched) {
            return {
                lastExecution: null,
                lastSuccess: null,
                lastDurationMs: null,
                successRuns24h: roll24?.successRuns24h ?? null,
                failureRuns24h: roll24?.failureRuns24h ?? null,
                avgDurationMs24h: roll24?.avgDurationMs24h ?? null
            };
        }
        const st = sched.getStatus();
        const lastAt = st.lastExecution?.get(jobName) ?? null;
        const meta = sched.getLastJobRunMeta(jobName);
        return {
            lastExecution: lastAt,
            lastSuccess: meta?.success ?? null,
            lastDurationMs: meta?.durationMs ?? null,
            successRuns24h: roll24?.successRuns24h ?? null,
            failureRuns24h: roll24?.failureRuns24h ?? null,
            avgDurationMs24h: roll24?.avgDurationMs24h ?? null
        };
    }
    getEvents(filters) {
        const { events, total } = this.repository.queryEvents(filters);
        const mapped = events.map(e => ({
            ...e,
            extra_data: TelemetryService.safeParseExtraData(e.extra_data)
        }));
        return {
            events: mapped,
            total,
            limit: filters.limit,
            offset: filters.offset,
            timestamp: new Date().toISOString()
        };
    }
    hasPriorWriteWithContentHash(ownerId, contentHash, sinceIso) {
        return this.repository.hasPriorWriteCompletedWithContentHash(ownerId, contentHash, sinceIso);
    }
}
//# sourceMappingURL=telemetry-service.js.map