/**
 * Copyright 2016 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 TraceContextManager = require('../trace/trace-context-manager'),
    conf = require('../conf/configure'),
    Logger = require('../logger'),
    AsyncSender = require('../udp/async_sender'),
    PacketTypeEnum = require('../udp/packet_type_enum');
const shimmer = require('../core/shimmer');

var CronObserver = function (agent) {
    this.agent = agent;
    this.packages = ['node-cron', 'cron', 'node-schedule'];
};

CronObserver.prototype.__createCronJobObserver = function (callback, cronExpression, jobName) {
    var self = this;

    // callback이 async 함수인지 확인
    const isAsync = callback.constructor.name === 'AsyncFunction';

    return function wrappedCronCallback() {
        return TraceContextManager._asyncLocalStorage.run(self.__initCtx(cronExpression, jobName), () => {
            var ctx = TraceContextManager._asyncLocalStorage.getStore();

            if (!ctx) {
                return callback.apply(this, arguments);
            }

            try {
                ctx.service_name = jobName || '/';
                let hostname = '0.0.0.0';

                let startDatas = [
                    hostname,
                    ctx.service_name,
                    '0.0.0.0',
                    '',
                    '',
                    String(ctx.userid || ""),
                    String(false),
                    ''
                ];

                AsyncSender.send_packet(PacketTypeEnum.TX_START, ctx, startDatas);

                // 원본 callback 실행
                const result = callback.apply(this, arguments);

                // async 함수인 경우 Promise 처리
                if (isAsync || (result && typeof result.then === 'function')) {
                    return Promise.resolve(result)
                        .then((res) => {
                            self.__endTransaction(null, ctx);
                            return res;
                        })
                        .catch((error) => {
                            self.__handleError(error, ctx);
                            self.__endTransaction(null, ctx);
                            throw error;
                        });
                } else {
                    // 동기 함수인 경우 바로 종료
                    self.__endTransaction(null, ctx);
                    return result;
                }
            } catch (error) {
                // 동기 에러 처리
                self.__handleError(error, ctx);
                self.__endTransaction(null, ctx);
                throw error;
            }
        });
    };
};

CronObserver.prototype.__handleError = function (error, ctx) {
    if (!ctx) return;

    try {
        ctx.error = 1;
        ctx.status = 500;

        var errorClass = error.code || error.name || error.constructor?.name || 'CronJobError';
        var errorMessage = error.message || 'Cron job execution failed';
        var errorStack = '';

        if (conf.trace_sql_error_stack && conf.trace_sql_error_depth && error.stack) {
            var traceDepth = conf.trace_sql_error_depth;
            var stackLines = error.stack.split("\n");
            if (stackLines.length > traceDepth) {
                stackLines = stackLines.slice(0, traceDepth + 1);
            }
            errorStack = stackLines.join("\n");
        }

        var errors = [errorClass, errorMessage];
        if (errorStack || error.stack) {
            errors.push(errorStack || error.stack);
        }

        AsyncSender.send_packet(PacketTypeEnum.TX_ERROR, ctx, errors);
    } catch (e) {
        Logger.printError('WHATAP-301', 'Error handling cron job error', e, false);
    }
};

CronObserver.prototype.__endTransaction = function (error, ctx) {
    try {
        if (error) {
            TraceContextManager.end(ctx != null ? ctx.id : null);
            ctx = null;
            return;
        }

        if (ctx == null || TraceContextManager.isExist(ctx.id) === false) {
            return;
        }

        let endDatas = [
            '0.0.0.0',
            ctx.service_name,
            0,  // mtid
            0,  // mdepth
            0,  // mcaller_txid
            0,  // mcaller_pcode
            '', // mcaller_spec
            String(0), // mcaller_url_hash
            ctx.status || 200
        ];

        ctx.start_time = Date.now();
        // ctx.elapsed = Date.now() - ctx.start_time;
        AsyncSender.send_packet(PacketTypeEnum.TX_END, ctx, endDatas);

        TraceContextManager.end(ctx.id);
        ctx = null;
    } catch (e) {
        Logger.printError('WHATAP-302', 'End cron transaction error', e, false);
        TraceContextManager.end(ctx != null ? ctx.id : null);
        ctx = null;
    }
};

CronObserver.prototype.__initCtx = function (cronExpression, jobName) {
    if (conf.getProperty('profile_enabled', true) === false) {
        return null;
    }

    if (conf.getProperty('trace_cron_enabled', false) === false) {
        return null;
    }

    var ctx = TraceContextManager.start();
    if (ctx == null) {
        return null;
    }

    ctx.service_name = jobName || '/';
    ctx.remoteIp = 0;
    ctx.userid = "";
    ctx.userAgentString = '';
    ctx.referer = '';
    ctx.status = 200;
    ctx.error = 0;

    return ctx;
};

/**
 * node-cron 라이브러리 후킹
 */
CronObserver.prototype.inject = function (mod, moduleName) {
    var self = this;

    if (mod.__whatap_observe__) {
        return;
    }
    mod.__whatap_observe__ = true;
    Logger.initPrint("CronObserver");

    if (conf.getProperty('profile_enabled', true) === false) {
        return;
    }

    // node-cron의 schedule 메서드 후킹
    if (typeof mod.schedule === 'function') {
        shimmer.wrap(mod, 'schedule', function (original) {
            return function wrappedSchedule(cronExpression, callback, options) {
                // 크론잡 이름 추출 (옵션에서 가져오거나 자동 생성)
                var jobName = '/';

                if (options && options.name) {
                    jobName = `/${options.name}`;
                } else if (typeof cronExpression === 'string') {
                    jobName = `/${cronExpression.replace(/\s+/g, '-')}`;
                }

                // callback을 래핑
                var wrappedCallback = self.__createCronJobObserver(callback, cronExpression, jobName);

                // 원본 schedule 호출 (래핑된 callback으로)
                return original.call(this, cronExpression, wrappedCallback, options);
            };
        });

    }

    // node-schedule 라이브러리 지원
    if (mod.scheduleJob && typeof mod.scheduleJob === 'function') {
        shimmer.wrap(mod, 'scheduleJob', function (original) {
            return function wrappedScheduleJob(nameOrSpec, specOrCallback, callback) {
                var jobName = '/';
                var spec, actualCallback;

                // 오버로딩 처리
                if (typeof nameOrSpec === 'string') {
                    jobName = `/${nameOrSpec}`;
                    spec = specOrCallback;
                    actualCallback = callback;
                } else {
                    spec = nameOrSpec;
                    actualCallback = specOrCallback;
                }

                // callback 래핑
                var wrappedCallback = self.__createCronJobObserver(
                    actualCallback,
                    JSON.stringify(spec),
                    jobName
                );

                // 원본 scheduleJob 호출
                if (typeof nameOrSpec === 'string') {
                    return original.call(this, nameOrSpec, spec, wrappedCallback);
                } else {
                    return original.call(this, spec, wrappedCallback);
                }
            };
        });
    }

    // cron 라이브러리 지원 (CronJob 클래스)
    if (mod.CronJob && typeof mod.CronJob === 'function') {
        // mod 객체의 'CronJob' 프로퍼티를 래핑
        shimmer.wrap(mod, 'CronJob', function(OriginalCronJob) {
            function WrappedCronJob(cronTime, onTick, onComplete, start, timezone, context, runOnInit, utcOffset, unrefTimeout) {
                // onTick 콜백 래핑
                var wrappedOnTick = null;
                if (typeof onTick === 'function') {
                    wrappedOnTick = self.__createCronJobObserver(
                        onTick,
                        typeof cronTime === 'string' ? cronTime : 'custom',
                        '/'
                    );
                }

                // 원본 생성자 호출
                return new OriginalCronJob(
                    cronTime,
                    wrappedOnTick || onTick,
                    onComplete,
                    start,
                    timezone,
                    context,
                    runOnInit,
                    utcOffset,
                    unrefTimeout
                );
            }

            Object.getOwnPropertyNames(OriginalCronJob).forEach(function (key) {
                if (key !== 'length' && key !== 'name' && key !== 'prototype') {
                    try {
                        WrappedCronJob[key] = OriginalCronJob[key];
                    } catch (e) {}
                }
            });

            // prototype 복사
            WrappedCronJob.prototype = OriginalCronJob.prototype;

            return WrappedCronJob;
        });
    }
};

exports.CronObserver = CronObserver;
