/**
 * Copyright 2025 the WHATAP project authors. All rights reserved.
 * Use of this source code is governed by a license that
 * can be found in the LICENSE file.
 */

var CounterTask     = require('../../lib/counter/task/counter-task'),
    conf = require('../../lib/conf/configure'),
    DateUtil = require('../../lib/util/dateutil'),
    Logger = require('../../lib/logger'),
    TagCountPack = require('../../lib/pack/tagcount-pack'),
    HashUtil = require('../../lib/util/hashutil'),
    SecurityMaster = require('../../lib/net/security-master'),
    NodeUtil = require('../../lib/util/nodeutil'),
    IPUtil = require('../../lib/util/iputil'),
    AsyncSender = require('../../lib/udp/async_sender'),
    DataOutputX = require('../../lib/io/data-outputx'),
    ParamPack = require('../pack/param-pack').ParamPack,
    MapValue = require('../value/map-value');

const { PerformanceObserver } = require('perf_hooks');
const DataOuputX = require('../../lib/io/data-outputx');

function GCAction() {
    CounterTask.call(this);
    this.isObserverSupported = false;
    this.checkGCObserverSupport();
}

GCAction.prototype = new CounterTask();
GCAction.prototype.constructor = GCAction;

GCAction.prototype.checkGCObserverSupport = function () {
    if (NodeUtil.hasStableGCObserver()) {
        this.isObserverSupported = true;
        Logger.print("WHATAP-GC-001", `GC Observer supported (Node.js ${NodeUtil.getNodeVersion()})`, false);
    } else {
        this.isObserverSupported = false;
        Logger.print("WHATAP-GC-002", `GC Observer not supported (Node.js ${NodeUtil.getNodeVersion()}, requires 16.7+ or 18+)`, false);
    }
};

GCAction.prototype.process = function () {
    try {
        if (!conf.getProperty('perfx_nodejs_gc_enabled', false) || !this.isObserverSupported) {
            return;
        }

        this.getNodeMemoryGC();
    } catch (e) {
        Logger.printError("WHATAP-GC-003", "NodeJS GC process error", e, false);
    }
};

GCAction.prototype.getNodeMemoryGC = function () {
    const gcStats = new Map();
    const self = this;

    try {
        // PerformanceObserver를 사용해서 현재까지의 GC 데이터 수집
        const observer = new PerformanceObserver((list) => {
            list.getEntries().forEach((entry) => {
                if (entry.entryType === 'gc') {
                    const gcName = self.getGCTypeName(entry.kind);

                    if (!gcStats.has(gcName)) {
                        gcStats.set(gcName, {
                            name: gcName,
                            collectionCount: 0,
                            collectionTime: 0
                        });
                    }

                    const stats = gcStats.get(gcName);
                    stats.collectionCount++;
                    stats.collectionTime += entry.duration;
                }
            });

            // 데이터 수집 완료 후 즉시 처리
            observer.disconnect();

            // 수집된 데이터가 있으면 전송
            if (gcStats.size > 0) {
                self.sendGCData(gcStats);
            }
        });

        observer.observe({ type: 'gc' });
    } catch (e) {
        Logger.printError("WHATAP-GC-004", "Failed to collect GC data", e, false);
    }
};

GCAction.prototype.sendGCData = function (gc_stats) {
    var p = new TagCountPack();
    p.pcode = SecurityMaster.PCODE;
    p.time = Math.floor(Date.now() / 1000) * 1000;
    p.category = "nodejsx_memory_gc";

    p.putTagString("oname", SecurityMaster.ONAME);
    p.putTagString("host_ip", IPUtil.getIp());
    p.putTagInt("pid", process.pid);
    p.putTagInt("!rectype", 2);

    const idLv = p.fields.internList("@id");
    const nameLv = p.fields.internList("name");
    const countLv = p.fields.internList("CollectionCount");
    const timeLv = p.fields.internList("CollectionTime");

    // GC 통계 데이터 추가
    for (const [gc_name, stats] of gc_stats) {
        idLv.addLong(HashUtil.hashFromString(gc_name));
        nameLv.addString(gc_name);
        countLv.addLong(stats.collectionCount);
        timeLv.addLong(Math.round(stats.collectionTime));
    }

    const bout = new DataOuputX();
    bout.writePack(p, null);
    const packbytes = bout.toByteArray();

    AsyncSender.send_relaypack(packbytes)
};

GCAction.prototype.execSystemGC = function () {
    var p = new ParamPack();
    p.pcode = SecurityMaster.PCODE;
    p.oid = SecurityMaster.OID;
    p.oname = SecurityMaster.ONAME;
    p.okind = SecurityMaster.OKIND;
    // p.oid
    try {
        var mem1 = process.memoryUsage();
        var total1 = mem1.heapTotal;
        var used1 = mem1.heapUsed;

        if (global && global.gc && typeof global.gc === 'function') {
            global.gc();
        } else {
            Logger.printError('WHATAP-601', 'Garbage collection unavailable.  Pass --expose-gc '
                + 'when launching node to enable forced garbage collection.', new Error());
        }

        var mem2 = process.memoryUsage();
        var total2 = mem2.heapTotal;
        var used2 = mem2.heapUsed;

        var mv = new MapValue();
        mv.putLong("before-t", total1);
        mv.putLong("before-u", used1);
        mv.putLong("after-t", total2);
        mv.putLong("after-u", used2);
        p.putValue("gc", mv);

        return p;
    } catch (e) {
        // p.putValue("error", e.toString());
        console.log(`e: ${e}`)
    }
    return null;
}

GCAction.prototype.getGCTypeName = function (kind) {
    const major = NodeUtil.getNodeMajorVersion();

    if (major >= 22) {
        switch (kind) {
            case 1: return 'V8 Scavenger';
            case 2: return 'V8 Minor Mark Sweep';
            case 4: return 'V8 Mark Sweep Compact';
            case 8: return 'V8 Incremental Marking';
            case 16: return 'V8 Process Weak Callbacks';
            case 31: return 'V8 All';
            default: return `V8 GC Type ${kind}`;
        }
    } else if (major >= 18) {
        switch (kind) {
            case 1: return 'V8 Scavenger';
            case 2: return 'V8 Minor Mark Compact';
            case 4: return 'V8 Mark Sweep Compact';
            case 8: return 'V8 Incremental Marking';
            case 16: return 'V8 Process Weak Callbacks';
            case 31: return 'V8 All';
            default: return `V8 GC Type ${kind}`;
        }
    } else {
        switch (kind) {
            case 1: return 'V8 Scavenger';
            case 2: return 'V8 Mark Sweep Compact';
            case 4: return 'V8 Incremental Marking';
            case 8: return 'V8 Process Weak Callbacks';
            case 15: return 'V8 All';
            default: return `V8 GC Type ${kind}`;
        }
    }
};


module.exports = GCAction;