/**
 * Remote Telemetry Engine
 *
 * Privacy-first telemetry for nexus-prime. DEFAULT: OFF.
 * Sends pseudonymous install + project activity to a Supabase REST endpoint
 * only after explicit opt-in or `NEXUS_TELEMETRY=on`.
 */
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as crypto from 'crypto';
import { fileURLToPath } from 'url';
import { NEXUS_PLG_EVENT_TYPES } from './nexus-signature.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const NEXUS_STATE_DIR = path.join(os.homedir(), '.nexus-prime');
const INSTALL_ID_PATH = path.join(NEXUS_STATE_DIR, 'install-id');
const CONSENT_PATH = path.join(NEXUS_STATE_DIR, 'telemetry-consent');
const QUEUE_PATH = path.join(NEXUS_STATE_DIR, 'telemetry-queue.json');
const STATE_PATH = path.join(NEXUS_STATE_DIR, 'telemetry-state.json');
const MAX_QUEUE_SIZE = 500;
const FLUSH_INTERVAL_MS = 5 * 60 * 1000;
const ACTIVE_INSTALL_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
function isPlainObject(value) {
    return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function sanitizePrimitive(value) {
    if (value === null || value === undefined)
        return undefined;
    if (typeof value === 'string')
        return value.slice(0, 200);
    if (typeof value === 'number' || typeof value === 'boolean')
        return value;
    if (Array.isArray(value)) {
        return value
            .map((entry) => sanitizePrimitive(entry))
            .filter((entry) => entry !== undefined)
            .slice(0, 24);
    }
    if (isPlainObject(value)) {
        const result = {};
        for (const [key, entry] of Object.entries(value)) {
            if (/(prompt|content|code|path|repo|branch|secret|token|file)/i.test(key))
                continue;
            const sanitized = sanitizePrimitive(entry);
            if (sanitized !== undefined)
                result[key] = sanitized;
        }
        return result;
    }
    return undefined;
}
function sanitizeEventData(data) {
    const result = {};
    for (const [key, value] of Object.entries(data)) {
        if (/(prompt|content|code|path|repo|branch|secret|token|file)/i.test(key))
            continue;
        const sanitized = sanitizePrimitive(value);
        if (sanitized !== undefined)
            result[key] = sanitized;
    }
    return result;
}
function createEmptyState() {
    return {
        installTracked: false,
        totalSessionsTracked: 0,
        totalTokensSaved: 0,
        totalFeatureUses: 0,
        totalMemoriesCreated: 0,
        totalMemoriesRecalled: 0,
        totalPrPresence: 0,
        totalCommitAttributions: 0,
        totalDocumentsSigned: 0,
        totalReleasesVerified: 0,
        totalProofBadgeViews: 0,
    };
}
export class RemoteTelemetry {
    installId;
    optedIn;
    endpoint;
    apiKey;
    queue = [];
    flushTimer = null;
    state = createEmptyState();
    constructor(options) {
        this.endpoint = options?.endpoint ?? process.env.NEXUS_TELEMETRY_ENDPOINT ?? '';
        this.apiKey = options?.apiKey ?? process.env.NEXUS_TELEMETRY_KEY ?? '';
        this.installId = this.getOrCreateUUID();
        this.optedIn = this.loadConsentState();
        this.loadQueue();
        this.loadState();
        if (this.isOptedIn() && !this.state.installTracked) {
            this.trackInstall();
        }
        this.startFlushTimer();
    }
    getOrCreateUUID() {
        try {
            if (fs.existsSync(INSTALL_ID_PATH)) {
                const id = fs.readFileSync(INSTALL_ID_PATH, 'utf8').trim();
                if (id.length >= 32)
                    return id;
            }
        }
        catch {
            // create new below
        }
        const uuid = crypto.randomUUID();
        try {
            fs.mkdirSync(NEXUS_STATE_DIR, { recursive: true });
            fs.writeFileSync(INSTALL_ID_PATH, uuid, 'utf8');
        }
        catch {
            // best effort
        }
        return uuid;
    }
    getInstallId() {
        return this.installId;
    }
    isOptedIn() {
        const envOverride = process.env.NEXUS_TELEMETRY;
        if (envOverride === 'on')
            return true;
        if (envOverride === 'off')
            return false;
        return this.optedIn;
    }
    setOptIn(value) {
        this.optedIn = value;
        if (!value) {
            this.queue = [];
            this.persistQueue();
        }
        const consent = {
            optedIn: value,
            timestamp: Date.now(),
            version: '2.0',
        };
        try {
            fs.mkdirSync(NEXUS_STATE_DIR, { recursive: true });
            fs.writeFileSync(CONSENT_PATH, JSON.stringify(consent, null, 2), 'utf8');
        }
        catch {
            // best effort
        }
        if (value && !this.state.installTracked) {
            this.trackInstall();
        }
    }
    loadConsentState() {
        try {
            if (fs.existsSync(CONSENT_PATH)) {
                const data = JSON.parse(fs.readFileSync(CONSENT_PATH, 'utf8'));
                return data.optedIn === true;
            }
        }
        catch {
            // default to off
        }
        return false;
    }
    trackInstall() {
        if (!this.isOptedIn())
            return;
        this.state.installTracked = true;
        this.persistState();
        this.enqueue({
            type: 'install',
            timestamp: Date.now(),
            installId: this.installId,
            data: {
                os: os.platform(),
                arch: os.arch(),
                nodeVersion: process.version,
                nexusVersion: this.getNexusVersion(),
            },
        });
    }
    trackSessionStart(projectId) {
        this.state.totalSessionsTracked += 1;
        this.state.lastSessionStartedAt = Date.now();
        this.persistState();
        this.enqueue({
            type: 'session_start',
            timestamp: Date.now(),
            installId: this.installId,
            data: {
                sessionNumber: this.state.totalSessionsTracked,
                projectHash: projectId ? this.hashIdentifier(projectId) : undefined,
            },
        });
    }
    trackSessionEnd(durationMs, stats = {}) {
        this.enqueue({
            type: 'session_end',
            timestamp: Date.now(),
            installId: this.installId,
            data: {
                durationMs: Math.max(0, Math.round(durationMs)),
                projectHash: stats.projectId ? this.hashIdentifier(stats.projectId) : undefined,
                tokensSaved: Number(stats.tokensSaved ?? 0),
                memoriesCreated: Number(stats.memoriesCreated ?? 0),
                memoriesRecalled: Number(stats.memoriesRecalled ?? 0),
                toolsUsed: Number(stats.toolsUsed ?? 0),
                sessionId: stats.sessionId ? String(stats.sessionId).slice(0, 64) : undefined,
            },
        });
        return this.flush();
    }
    trackFeatureUsage(feature, metadata) {
        this.state.totalFeatureUses += 1;
        this.persistState();
        this.enqueue({
            type: 'feature_use',
            timestamp: Date.now(),
            installId: this.installId,
            data: {
                feature,
                ...sanitizeEventData(metadata ?? {}),
            },
        });
    }
    trackTokenSavings(savings, metadata) {
        const normalized = Math.max(0, Number(savings || 0));
        this.state.totalTokensSaved += normalized;
        this.persistState();
        this.enqueue({
            type: 'token_savings',
            timestamp: Date.now(),
            installId: this.installId,
            data: {
                savings: normalized,
                lifetimeSavings: this.state.totalTokensSaved,
                ...sanitizeEventData(metadata ?? {}),
            },
        });
    }
    trackMemoryCreated(scope, metadata) {
        this.state.totalMemoriesCreated += 1;
        this.persistState();
        this.enqueue({
            type: 'memory_created',
            timestamp: Date.now(),
            installId: this.installId,
            data: {
                scope,
                ...sanitizeEventData(metadata ?? {}),
            },
        });
    }
    trackMemoryRecalled(hitCount, metadata) {
        const normalizedHits = Math.max(0, Number(hitCount || 0));
        this.state.totalMemoriesRecalled += normalizedHits;
        this.persistState();
        this.enqueue({
            type: 'memory_recalled',
            timestamp: Date.now(),
            installId: this.installId,
            data: {
                hitCount: normalizedHits,
                ...sanitizeEventData(metadata ?? {}),
            },
        });
    }
    trackAutonomyEvent(type, metadata) {
        this.enqueue({
            type,
            timestamp: Date.now(),
            installId: this.installId,
            data: sanitizeEventData(metadata ?? {}),
        });
    }
    trackProductEvent(type, metadata) {
        this.incrementProductCounter(type);
        this.persistState();
        this.enqueue({
            type,
            timestamp: Date.now(),
            installId: this.installId,
            data: sanitizeEventData({
                eventFamily: 'nexus_prime_proof',
                ...metadata,
            }),
        });
    }
    enqueue(event) {
        if (!this.isOptedIn())
            return;
        this.queue.push({
            ...event,
            data: sanitizeEventData(event.data),
        });
        if (this.queue.length > MAX_QUEUE_SIZE) {
            this.queue = this.queue.slice(-MAX_QUEUE_SIZE);
        }
        this.persistQueue();
    }
    persistQueue() {
        try {
            fs.mkdirSync(NEXUS_STATE_DIR, { recursive: true });
            fs.writeFileSync(QUEUE_PATH, JSON.stringify(this.queue, null, 2), 'utf8');
        }
        catch {
            // best effort
        }
    }
    loadQueue() {
        try {
            if (fs.existsSync(QUEUE_PATH)) {
                const parsed = JSON.parse(fs.readFileSync(QUEUE_PATH, 'utf8'));
                this.queue = Array.isArray(parsed) ? parsed : [];
            }
        }
        catch {
            this.queue = [];
        }
    }
    persistState() {
        try {
            fs.mkdirSync(NEXUS_STATE_DIR, { recursive: true });
            fs.writeFileSync(STATE_PATH, JSON.stringify(this.state, null, 2), 'utf8');
        }
        catch {
            // best effort
        }
    }
    loadState() {
        try {
            if (fs.existsSync(STATE_PATH)) {
                const parsed = JSON.parse(fs.readFileSync(STATE_PATH, 'utf8'));
                this.state = {
                    ...createEmptyState(),
                    ...(isPlainObject(parsed) ? parsed : {}),
                };
            }
        }
        catch {
            this.state = createEmptyState();
        }
    }
    async flush() {
        if (!this.isOptedIn()) {
            return { sent: 0, error: 'Telemetry is disabled (opt-in required)' };
        }
        if (!this.endpoint) {
            return { sent: 0, error: 'No telemetry endpoint configured (set NEXUS_TELEMETRY_ENDPOINT)' };
        }
        if (!this.apiKey) {
            return { sent: 0, error: 'No telemetry API key configured (set NEXUS_TELEMETRY_KEY)' };
        }
        if (this.queue.length === 0) {
            return { sent: 0 };
        }
        const batch = [...this.queue];
        const rows = batch.map((event) => ({
            install_id: event.installId,
            event_type: event.type,
            event_data: {
                ...event.data,
                installId: undefined,
            },
            sent_at: new Date(event.timestamp).toISOString(),
        }));
        try {
            const response = await fetch(this.endpoint, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    apikey: this.apiKey,
                    Authorization: `Bearer ${this.apiKey}`,
                    Prefer: 'return=minimal',
                },
                body: JSON.stringify(rows),
                signal: AbortSignal.timeout(10_000),
            });
            if (!response.ok) {
                return { sent: 0, error: `HTTP ${response.status}` };
            }
            this.queue = this.queue.slice(batch.length);
            this.state.lastFlushAt = Date.now();
            this.persistQueue();
            this.persistState();
            return { sent: batch.length };
        }
        catch (err) {
            return { sent: 0, error: err?.message ?? 'Network error' };
        }
    }
    async fetchRemoteStats() {
        const serviceKey = process.env.NEXUS_TELEMETRY_SERVICE_KEY ?? '';
        if (!this.endpoint || !serviceKey) {
            return {
                configured: false,
                totalInstalls: 0,
                activeInstalls7d: 0,
                totalSessions: 0,
                totalTokenSavings: 0,
                totalMemoriesCreated: 0,
                totalMemoriesRecalled: 0,
                totalPrPresence: 0,
                totalCommitAttributions: 0,
                totalDocumentsSigned: 0,
                totalReleasesVerified: 0,
                totalProofBadgeViews: 0,
                installs: [],
            };
        }
        try {
            const url = new URL(this.endpoint);
            url.searchParams.set('select', 'install_id,event_type,event_data,created_at,sent_at');
            url.searchParams.set('order', 'sent_at.desc');
            const response = await fetch(url, {
                headers: {
                    apikey: serviceKey,
                    Authorization: `Bearer ${serviceKey}`,
                },
                signal: AbortSignal.timeout(10_000),
            });
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }
            const rows = await response.json();
            const installs = new Map();
            const activeInstalls = new Set();
            const installIds = new Set();
            let totalSessions = 0;
            let totalTokenSavings = 0;
            let totalMemoriesCreated = 0;
            let totalMemoriesRecalled = 0;
            let totalPrPresence = 0;
            let totalCommitAttributions = 0;
            let totalDocumentsSigned = 0;
            let totalReleasesVerified = 0;
            let totalProofBadgeViews = 0;
            const activeCutoff = Date.now() - ACTIVE_INSTALL_WINDOW_MS;
            for (const row of rows) {
                const installId = String(row.install_id ?? '').trim();
                if (!installId)
                    continue;
                installIds.add(installId);
                const sentAt = row.sent_at ?? row.created_at;
                const sentAtMs = sentAt ? Date.parse(sentAt) : NaN;
                if (!Number.isNaN(sentAtMs) && sentAtMs >= activeCutoff) {
                    activeInstalls.add(installId);
                }
                const bucket = installs.get(installId) ?? {
                    installId,
                    sessions: 0,
                    tokenSavings: 0,
                    memoriesCreated: 0,
                    memoriesRecalled: 0,
                    featureUses: 0,
                    autonomyEvents: 0,
                    proofEvents: 0,
                    recentProjects: [],
                };
                if (!bucket.lastSeenAt || (sentAt && Date.parse(bucket.lastSeenAt) < sentAtMs)) {
                    bucket.lastSeenAt = sentAt;
                }
                const data = isPlainObject(row.event_data) ? row.event_data : {};
                const projectHash = typeof data.projectHash === 'string' ? data.projectHash : undefined;
                if (projectHash && !bucket.recentProjects.includes(projectHash)) {
                    bucket.recentProjects = [...bucket.recentProjects, projectHash].slice(-5);
                }
                switch (row.event_type) {
                    case 'session_start':
                        totalSessions += 1;
                        bucket.sessions += 1;
                        break;
                    case 'token_savings': {
                        const savings = Number(data.savings ?? 0);
                        totalTokenSavings += savings;
                        bucket.tokenSavings += savings;
                        break;
                    }
                    case 'memory_created':
                        totalMemoriesCreated += 1;
                        bucket.memoriesCreated += 1;
                        break;
                    case 'memory_recalled': {
                        const recalled = Number(data.hitCount ?? 0);
                        totalMemoriesRecalled += recalled;
                        bucket.memoriesRecalled += recalled;
                        break;
                    }
                    case 'feature_use':
                        bucket.featureUses += 1;
                        break;
                    case 'pr_presence_added':
                        totalPrPresence += 1;
                        bucket.proofEvents += 1;
                        break;
                    case 'commit_attributed':
                        totalCommitAttributions += 1;
                        bucket.proofEvents += 1;
                        break;
                    case 'document_signed':
                        totalDocumentsSigned += 1;
                        bucket.proofEvents += 1;
                        break;
                    case 'release_verified':
                        totalReleasesVerified += 1;
                        bucket.proofEvents += 1;
                        break;
                    case 'proof_badge_viewed':
                    case 'website_proof_viewed':
                        totalProofBadgeViews += 1;
                        bucket.proofEvents += 1;
                        break;
                    case 'telemetry_batch_sent':
                        bucket.proofEvents += 1;
                        break;
                    case 'autonomy_candidate':
                    case 'autonomy_candidate_suppressed':
                    case 'autonomy_apply_started':
                    case 'autonomy_apply_finished':
                    case 'autonomy_apply_blocked':
                        bucket.autonomyEvents += 1;
                        break;
                    default:
                        break;
                }
                installs.set(installId, bucket);
            }
            return {
                configured: true,
                totalInstalls: installIds.size,
                activeInstalls7d: activeInstalls.size,
                totalSessions,
                totalTokenSavings,
                totalMemoriesCreated,
                totalMemoriesRecalled,
                totalPrPresence,
                totalCommitAttributions,
                totalDocumentsSigned,
                totalReleasesVerified,
                totalProofBadgeViews,
                installs: [...installs.values()]
                    .sort((a, b) => (b.tokenSavings - a.tokenSavings) || (b.sessions - a.sessions))
                    .slice(0, 25),
            };
        }
        catch {
            return {
                configured: false,
                totalInstalls: 0,
                activeInstalls7d: 0,
                totalSessions: 0,
                totalTokenSavings: 0,
                totalMemoriesCreated: 0,
                totalMemoriesRecalled: 0,
                totalPrPresence: 0,
                totalCommitAttributions: 0,
                totalDocumentsSigned: 0,
                totalReleasesVerified: 0,
                totalProofBadgeViews: 0,
                installs: [],
            };
        }
    }
    startFlushTimer() {
        if (this.flushTimer)
            return;
        this.flushTimer = setInterval(() => {
            this.flush().catch(() => {
                // silent best effort
            });
        }, FLUSH_INTERVAL_MS);
        if (typeof this.flushTimer.unref === 'function') {
            this.flushTimer.unref();
        }
    }
    getStats() {
        return {
            installId: this.installId,
            optedIn: this.isOptedIn(),
            queuedEvents: this.queue.length,
            totalSessionsTracked: this.state.totalSessionsTracked,
            totalTokensSaved: this.state.totalTokensSaved,
            totalFeatureUses: this.state.totalFeatureUses,
            totalMemoriesCreated: this.state.totalMemoriesCreated,
            totalMemoriesRecalled: this.state.totalMemoriesRecalled,
            totalPrPresence: this.state.totalPrPresence,
            totalCommitAttributions: this.state.totalCommitAttributions,
            totalDocumentsSigned: this.state.totalDocumentsSigned,
            totalReleasesVerified: this.state.totalReleasesVerified,
            totalProofBadgeViews: this.state.totalProofBadgeViews,
            endpointConfigured: Boolean(this.endpoint && this.apiKey),
            remoteStatsConfigured: Boolean(this.endpoint && process.env.NEXUS_TELEMETRY_SERVICE_KEY),
            lastFlushAt: this.state.lastFlushAt,
        };
    }
    getNexusVersion() {
        try {
            const pkgPath = path.resolve(__dirname, '..', '..', 'package.json');
            const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
            return String(pkg.version || 'unknown');
        }
        catch {
            return 'unknown';
        }
    }
    hashIdentifier(value) {
        return crypto.createHash('sha256').update(String(value)).digest('hex').slice(0, 12);
    }
    incrementProductCounter(type) {
        if (!NEXUS_PLG_EVENT_TYPES.includes(type))
            return;
        switch (type) {
            case 'pr_presence_added':
                this.state.totalPrPresence += 1;
                break;
            case 'commit_attributed':
                this.state.totalCommitAttributions += 1;
                break;
            case 'document_signed':
                this.state.totalDocumentsSigned += 1;
                break;
            case 'release_verified':
                this.state.totalReleasesVerified += 1;
                break;
            case 'proof_badge_viewed':
            case 'website_proof_viewed':
                this.state.totalProofBadgeViews += 1;
                break;
            case 'telemetry_batch_sent':
                break;
        }
    }
    destroy() {
        if (this.flushTimer) {
            clearInterval(this.flushTimer);
            this.flushTimer = null;
        }
        this.persistQueue();
        this.persistState();
    }
}
let _sharedTelemetry = null;
export function getSharedTelemetry(options) {
    if (!_sharedTelemetry) {
        _sharedTelemetry = new RemoteTelemetry(options);
    }
    return _sharedTelemetry;
}
