'use strict';

/**
 * LLM Metric counter tasks (7 categories):
 *  - llm_active_stat
 *  - llm_api_status
 *  - llm_error_stat
 *  - llm_feature_stat
 *  - llm_perf_stat   (sketch fields omitted; Node has no datasketches)
 *  - llm_token_usage
 *  - meter (LLM_TRANSACTION)
 *
 * Each task exposes a process() method invoked by counter-manager.
 * Packs are sent via async_sender.send_llm_relaypack to the LLM Go agent.
 */

const TagCountPack = require('../../pack/tagcount-pack');
const DataOutputX = require('../../io/data-outputx');
const AsyncSender = require('../../udp/async_sender');
const conf = require('../../conf/configure');
const Logger = require('../../logger');
const HashUtil = require('../../util/hashutil');
const collector = require('../../llm/llm-stat-collector');
const { getLlmPcode } = require('../../llm/llm-pcode');

const _PID = process.pid;

function _enabled() {
    return conf.getProperty('llm_enabled', false);
}

function _bucketTime(ms) {
    return Math.floor(ms / 5000) * 5000;
}

function _send(p) {
    try {
        p.pcode = getLlmPcode();
        const bout = new DataOutputX();
        bout.writePack(p, null);
        AsyncSender.send_llm_relaypack(bout.toByteArray());
    } catch (e) {
        Logger.printError('WHATAP-LLM-STAT-001', 'send pack failed', e, false);
    }
}

function _newPack(category, model, provider, opType, url) {
    const p = new TagCountPack();
    p.time = _bucketTime(Date.now());
    p.category = category;
    p.tags.putLong('pid', _PID);
    p.tags.putString('model', model || 'unknown');
    p.tags.putString('provider', provider || '');
    p.tags.putString('operation_type', opType || 'unknown');
    p.tags.putString('url', url || '');
    return p;
}

// ---------------------------------------------------------
// 1) llm_active_stat
// ---------------------------------------------------------
function LLMActiveStatTask() {}
LLMActiveStatTask.prototype.process = function () {
    if (!_enabled()) return;
    // 매 tick에 model cache TTL 만료 entry 제거 (10분/500개 cap)
    try { collector.pruneModelCache(); } catch (e) {}

    const snap = collector.snapshotAndResetActive();
    if (snap.active.size === 0) return;
    snap.active.forEach((count, k) => {
        try {
            const parsed = collector.parseKey(k);
            const info = snap.modelInfo.get(parsed.model);
            const provider = info ? info.provider : '';
            const url = info ? info.url : '';
            const p = _newPack('llm_active_stat', parsed.model, provider, parsed.op_type, url);
            p.fields.putLong('count', count);
            _send(p);
        } catch (e) {
            Logger.printError('WHATAP-LLM-STAT-010', 'active stat error', e, false);
        }
    });
};

// ---------------------------------------------------------
// 2) llm_api_status
// ---------------------------------------------------------
function LLMApiStatusTask() {}
LLMApiStatusTask.prototype.process = function () {
    if (!_enabled()) return;
    const snap = collector.snapshotAndResetApiStatus();
    if (snap.x4.size === 0 && snap.x5.size === 0) return;

    // Set spread → Map iteration으로 GC 압박 감소 (intermediate array 제거)
    const seen = new Set();
    const emit = (k) => {
        if (seen.has(k)) return;
        seen.add(k);
        try {
            const parsed = collector.parseKey(k);
            const p = _newPack('llm_api_status', parsed.model, parsed.provider, parsed.op_type, parsed.url);
            p.fields.putLong('4xx_total_count', snap.x4.get(k) || 0);
            p.fields.putLong('5xx_total_count', snap.x5.get(k) || 0);
            _send(p);
        } catch (e) {
            Logger.printError('WHATAP-LLM-STAT-020', 'api status error', e, false);
        }
    };
    snap.x4.forEach((_, k) => emit(k));
    snap.x5.forEach((_, k) => emit(k));
};

// ---------------------------------------------------------
// 3) llm_error_stat
// ---------------------------------------------------------
function LLMErrorStatTask() {}
LLMErrorStatTask.prototype.process = function () {
    if (!_enabled()) return;
    const snap = collector.snapshotAndResetErrors();
    if (snap.api.size === 0 && snap.program.size === 0 && snap.lastApi.size === 0) return;

    const seen = new Set();
    const emit = (k) => {
        if (seen.has(k)) return;
        seen.add(k);
        try {
            const parsed = collector.parseKey(k);
            const apiE = snap.api.get(k) || 0;
            const progE = snap.program.get(k) || 0;
            const p = _newPack('llm_error_stat', parsed.model, parsed.provider, parsed.op_type, parsed.url);
            p.fields.putLong('error_count', apiE + progE);
            p.fields.putLong('api_error_count', apiE);
            p.fields.putLong('program_error_count', progE);
            p.fields.putLong('last_api_error_count', snap.lastApi.get(k) || 0);
            _send(p);
        } catch (e) {
            Logger.printError('WHATAP-LLM-STAT-030', 'error stat error', e, false);
        }
    };
    snap.api.forEach((_, k) => emit(k));
    snap.program.forEach((_, k) => emit(k));
    snap.lastApi.forEach((_, k) => emit(k));
};

// ---------------------------------------------------------
// 4) llm_feature_stat (리스트형 → !rectype=2)
// ---------------------------------------------------------
function LLMFeatureStatTask() {}
LLMFeatureStatTask.prototype.process = function () {
    if (!_enabled()) return;
    const snap = collector.snapshotAndResetFeatures();
    if (snap.calls.size === 0) return;
    snap.calls.forEach((callCount, k) => {
        try {
            const parsed = collector.parseKey(k);
            const p = _newPack('llm_feature_stat', parsed.model, parsed.provider, parsed.op_type, parsed.url);
            p.tags.putLong('!rectype', 2);
            p.fields.putLong('call_count', callCount);

            const idList = p.fields.newList('@id');
            const fList = p.fields.newList('features');
            const fCntList = p.fields.newList('features_count');
            const counts = snap.counts.get(k) || {};
            for (const feat of collector.FEATURES) {
                const c = counts[feat] || 0;
                if (c > 0) {
                    idList.addLong(HashUtil.hashFromString(`${parsed.model}:${parsed.provider}:${parsed.op_type}:${feat}`));
                    fList.addString(feat);
                    fCntList.addLong(c);
                }
            }
            _send(p);
        } catch (e) {
            Logger.printError('WHATAP-LLM-STAT-040', 'feature stat error', e, false);
        }
    });
};

// ---------------------------------------------------------
// 5) llm_perf_stat (sketch 미지원 — sum/count만 전송)
// ---------------------------------------------------------
function LLMPerfStatTask() {}
LLMPerfStatTask.prototype.process = function () {
    if (!_enabled()) return;
    const snap = collector.snapshotAndResetPerf();
    if (snap.callCount.size === 0) return;
    snap.callCount.forEach((call, k) => {
        try {
            const parsed = collector.parseKey(k);
            const p = _newPack('llm_perf_stat', parsed.model, parsed.provider, parsed.op_type, parsed.url);
            p.fields.putLong('call_count', call);
            p.fields.putFloat('latency_sum', snap.latencySum.get(k) || 0);
            p.fields.putFloat('ttft_sum', snap.ttftSum.get(k) || 0);
            p.fields.putLong('ttft_count', snap.ttftCount.get(k) || 0);
            p.fields.putFloat('tpot_sum', snap.tpotSum.get(k) || 0);
            p.fields.putLong('tpot_count', snap.tpotCount.get(k) || 0);
            // sketch 필드는 datasketches 미지원으로 omit
            _send(p);
        } catch (e) {
            Logger.printError('WHATAP-LLM-STAT-050', 'perf stat error', e, false);
        }
    });
};

// ---------------------------------------------------------
// 6) llm_token_usage (리스트형 → !rectype=2)
// ---------------------------------------------------------
const _TOKEN_TYPES = ['input_tokens', 'output_tokens', 'cached_tokens', 'reasoning_tokens',
    'cache_creation_input_tokens', 'cache_read_input_tokens'];
const _TOKEN_COST_MAP = {
    'input_tokens': 'input_cost',
    'output_tokens': 'output_cost',
    'cached_tokens': 'cached_cost',
};

function LLMTokenUsageTask() {}
LLMTokenUsageTask.prototype.process = function () {
    if (!_enabled()) return;
    const snap = collector.snapshotAndResetTokens();
    if (snap.calls.size === 0) return;
    snap.calls.forEach((call, k) => {
        try {
            const parsed = collector.parseKey(k);
            const counts = snap.counts.get(k) || {};
            const costs = snap.costs.get(k) || {};
            const totalTokens = (counts.input_tokens || 0) + (counts.output_tokens || 0);
            const totalCost = (costs.input_cost || 0) + (costs.output_cost || 0);

            const p = _newPack('llm_token_usage', parsed.model, parsed.provider, parsed.op_type, parsed.url);
            p.tags.putLong('!rectype', 2);
            p.fields.putLong('call_count', call);
            p.fields.putLong('total_tokens', totalTokens);
            p.fields.putFloat('total_cost', +totalCost.toFixed(6));
            p.fields.putLong('error_count', snap.error.get(k) || 0);
            p.fields.putLong('stream_count', snap.stream.get(k) || 0);

            const idList = p.fields.newList('@id');
            const tokList = p.fields.newList('tokens');
            const cntList = p.fields.newList('tokens_count');
            const costList = p.fields.newList('tokens_cost');
            for (const tt of _TOKEN_TYPES) {
                const c = counts[tt] || 0;
                if (c > 0) {
                    idList.addLong(HashUtil.hashFromString(`${parsed.model}:${parsed.provider}:${parsed.op_type}:${tt}`));
                    tokList.addString(tt);
                    cntList.addLong(c);
                    const costKey = _TOKEN_COST_MAP[tt];
                    costList.addFloat(costKey ? +(costs[costKey] || 0).toFixed(6) : 0);
                }
            }
            _send(p);
        } catch (e) {
            Logger.printError('WHATAP-LLM-STAT-060', 'token usage error', e, false);
        }
    });
};

// LLM 호출 카운트는 _llmMeterCount로 유지되어 ACTIVE_STATS 패킷 꼬리에
// "LLM:count=N" 형태로 append 송신됨 (udp_session.js:161).

module.exports = {
    LLMActiveStatTask,
    LLMApiStatusTask,
    LLMErrorStatTask,
    LLMFeatureStatTask,
    LLMPerfStatTask,
    LLMTokenUsageTask,
};
