/**
 * Kafka Observer for KafkaJS
 *
 * Producer: send()/sendBatch() 메서드를 후킹하여 TX_HTTPC 스텝 정보를 전송합니다.
 * Consumer: run().eachMessage 콜백을 후킹하여 새 트랜잭션을 시작합니다.
 *
 * Copyright WHATAP project authors. All rights reserved.
 */

var TraceContextManager = require('../trace/trace-context-manager'),
    conf = require('../conf/configure'),
    KeyGen = require('../util/keygen'),
    TraceHelper = require('../util/trace-helper'),
    Logger = require('../logger'),
    AsyncSender = require('../udp/async_sender'),
    PacketTypeEnum = require('../udp/packet_type_enum'),
    TraceHttpc = require('../trace/trace-httpc'),
    shimmer = require('../core/shimmer');

// Kafka 인스턴스별 brokers 정보 저장
var kafkaBrokersMap = new WeakMap();

var KafkaObserver = function (agent) {
    this.agent = agent;
    this.packages = ['kafkajs'];
};

KafkaObserver.prototype.inject = function (mod, moduleName) {
    var self = this;

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

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

    // Kafka 클래스 생성자 후킹 (brokers 정보 저장)
    var OriginalKafka = mod.Kafka;
    if (OriginalKafka) {
        mod.Kafka = function WrappedKafka(config) {
            var instance = new OriginalKafka(config);

            // brokers 정보 저장
            if (config && config.brokers) {
                var brokers = config.brokers;
                var brokerString = Array.isArray(brokers) ? brokers.join(',') : String(brokers);
                kafkaBrokersMap.set(instance, brokerString);
            }

            return instance;
        };

        // 프로토타입 및 static 속성 복사
        mod.Kafka.prototype = OriginalKafka.prototype;
        Object.setPrototypeOf(mod.Kafka, OriginalKafka);
        Object.keys(OriginalKafka).forEach(function (key) {
            try {
                mod.Kafka[key] = OriginalKafka[key];
            } catch (e) {}
        });
    }

    // Kafka.prototype.producer 후킹
    if (mod.Kafka && mod.Kafka.prototype) {
        shimmer.wrap(mod.Kafka.prototype, 'producer', function (originalProducer) {
            return function wrappedProducer(producerConfig) {
                // WeakMap에서 brokers 정보 가져오기
                var brokerString = kafkaBrokersMap.get(this) || 'kafka';

                // 원본 producer 호출
                var producer = originalProducer.apply(this, arguments);

                // producer.send 후킹
                if (producer && producer.send) {
                    shimmer.wrap(producer, 'send', function (originalSend) {
                        return function wrappedSend(sendConfig) {
                            return handleSend(originalSend, this, sendConfig, brokerString);
                        };
                    });
                }

                // producer.sendBatch 후킹
                if (producer && producer.sendBatch) {
                    shimmer.wrap(producer, 'sendBatch', function (originalSendBatch) {
                        return function wrappedSendBatch(sendBatchConfig) {
                            return handleSendBatch(originalSendBatch, this, sendBatchConfig, brokerString);
                        };
                    });
                }

                return producer;
            };
        });
    }

    // Kafka.prototype.consumer 후킹
    if (mod.Kafka && mod.Kafka.prototype) {
        shimmer.wrap(mod.Kafka.prototype, 'consumer', function (originalConsumer) {
            return function wrappedConsumer(consumerConfig) {
                var brokerString = kafkaBrokersMap.get(this) || 'kafka';

                // 원본 consumer 호출
                var consumer = originalConsumer.apply(this, arguments);

                // consumer.run 후킹
                if (consumer && consumer.run) {
                    shimmer.wrap(consumer, 'run', function (originalRun) {
                        return function wrappedRun(runConfig) {
                            if (runConfig && runConfig.eachMessage) {
                                var originalEachMessage = runConfig.eachMessage;
                                runConfig.eachMessage = function wrappedEachMessage(payload) {
                                    return handleConsumerMessage(originalEachMessage, payload, brokerString);
                                };
                            }
                            return originalRun.call(this, runConfig);
                        };
                    });
                }

                return consumer;
            };
        });
    }

    /**
     * Kafka Consumer 컨텍스트 초기화
     */
    function initKafkaConsumerCtx(topic) {
        // 프로파일링이 비활성화된 경우
        if (conf.getProperty('profile_enabled', true) === false) {
            return null;
        }

        // Kafka Consumer 트랜잭션이 비활성화된 경우
        if (conf.getProperty('trace_kafka_consumer_enabled', false) === false) {
            return null;
        }

        // 새 트랜잭션 컨텍스트 생성
        var ctx = TraceContextManager.start();
        if (!ctx) {
            return null;
        }

        // 서비스 이름 설정 (topic 기반)
        ctx.service_name = '/kafka/' + topic;

        // 기본값 설정
        ctx.remoteIp = 0;
        ctx.userid = "";
        ctx.userAgentString = '';
        ctx.referer = '';
        ctx.host = '';
        ctx.status = 200;
        ctx.error = 0;

        return ctx;
    }

    /**
     * Consumer 메시지 핸들러 - 새 트랜잭션 시작
     */
    function handleConsumerMessage(originalEachMessage, payload, brokerString) {
        var topic = payload.topic || 'unknown';

        return TraceContextManager._asyncLocalStorage.run(initKafkaConsumerCtx(topic), function() {
            var ctx = TraceContextManager._asyncLocalStorage.getStore();
            if (!ctx) {
                return originalEachMessage(payload);
            }

            try {
                // TX_START 패킷 전송
                var startDatas = [
                    brokerString || 'kafka',  // hostname
                    ctx.service_name,          // service_name
                    '0.0.0.0',                 // remoteIp
                    '',                        // userAgent
                    '',                        // referer
                    String(ctx.userid || ""), // wclientid
                    String(false),             // is_static
                    ''                         // extra
                ];
                AsyncSender.send_packet(PacketTypeEnum.TX_START, ctx, startDatas);

                // 원본 eachMessage 호출
                var result = originalEachMessage(payload);

                // Promise 처리 (async eachMessage 지원)
                if (result && typeof result.then === 'function') {
                    return result.then(
                        function(res) {
                            endConsumerTransaction(ctx, null);
                            return res;
                        },
                        function(err) {
                            handleConsumerError(ctx, err);
                            endConsumerTransaction(ctx, null);
                            throw err;
                        }
                    );
                } else {
                    // 동기 함수인 경우
                    endConsumerTransaction(ctx, null);
                    return result;
                }
            } catch (error) {
                // 동기 에러 처리
                handleConsumerError(ctx, error);
                endConsumerTransaction(ctx, null);
                throw error;
            }
        });
    }

    /**
     * Consumer 에러 처리
     */
    function handleConsumerError(ctx, error) {
        if (!ctx) return;

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

            var errorClass = error.code || error.name || 'KafkaConsumerError';
            var errorMessage = error.message || 'Kafka consumer error';
            var errors = [errorClass, errorMessage];

            if (error.stack) {
                errors.push(error.stack);
            }

            AsyncSender.send_packet(PacketTypeEnum.TX_ERROR, ctx, errors);
        } catch (e) {
            Logger.printError('WHATAP-254', 'Kafka Consumer error handling failed', e, false);
        }
    }

    /**
     * Consumer 트랜잭션 종료
     */
    function endConsumerTransaction(ctx, error) {
        try {
            if (error) {
                TraceContextManager.end(ctx != null ? ctx.id : null);
                return;
            }

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

            // TX_END 패킷 전송
            var endDatas = [
                '0.0.0.0',           // remoteIp
                ctx.service_name,    // service_name
                '0',                 // mtid
                0,                   // mdepth
                '0',                 // mcaller_txid
                0,                   // mcaller_pcode
                '',                  // mcaller_spec
                '0',                 // mcaller_url_hash
                ctx.status || 200    // status
            ];

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

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

    /**
     * Producer.send() 핸들러
     */
    function handleSend(originalSend, thisArg, sendConfig, brokerString) {
        var ctx = TraceContextManager.getCurrentContext();
        if (!ctx) {
            return originalSend.call(thisArg, sendConfig);
        }

        var topic = sendConfig.topic || 'unknown';

        ctx.start_time = Date.now();

        try {
            ctx.mcallee = KeyGen.next();

            // Kafka 정보 설정
            ctx.httpc_url = topic;
            ctx.httpc_host = brokerString || 'kafka';
            ctx.httpc_port = 9092;
            ctx.active_httpc_hash = true;

        } catch (e) {
            Logger.printError('WHATAP-250', 'Kafka Setup Error', e, false);
            return originalSend.call(thisArg, sendConfig);
        }

        // 원본 메서드 호출
        var result = originalSend.call(thisArg, sendConfig);

        // Promise 처리
        if (result && typeof result.then === 'function') {
            return result.then(
                function (res) {
                    endKafkaCall(ctx, null);
                    return res;
                },
                function (err) {
                    endKafkaCall(ctx, err);
                    throw err;
                }
            );
        } else {
            endKafkaCall(ctx, null);
            return result;
        }
    }

    /**
     * Producer.sendBatch() 핸들러
     */
    function handleSendBatch(originalSendBatch, thisArg, sendBatchConfig, brokerString) {
        var ctx = TraceContextManager.getCurrentContext();
        if (!ctx) {
            return originalSendBatch.call(thisArg, sendBatchConfig);
        }

        var topicMessages = sendBatchConfig.topicMessages || [];
        var topics = topicMessages.map(function (tm) { return tm.topic; }).join(',');

        ctx.start_time = Date.now();

        try {
            ctx.mcallee = KeyGen.next();

            // Kafka 정보 설정
            ctx.httpc_url = topics || 'batch';
            ctx.httpc_host = brokerString || 'kafka';
            ctx.httpc_port = 9092;
            ctx.active_httpc_hash = true;

        } catch (e) {
            Logger.printError('WHATAP-251', 'Kafka SendBatch Setup Error', e, false);
            return originalSendBatch.call(thisArg, sendBatchConfig);
        }

        // 원본 메서드 호출
        var result = originalSendBatch.call(thisArg, sendBatchConfig);

        // Promise 처리
        if (result && typeof result.then === 'function') {
            return result.then(
                function (res) {
                    endKafkaCall(ctx, null);
                    return res;
                },
                function (err) {
                    endKafkaCall(ctx, err);
                    throw err;
                }
            );
        } else {
            endKafkaCall(ctx, null);
            return result;
        }
    }

    /**
     * Kafka 호출 종료 및 TX_HTTPC 패킷 전송
     */
    function endKafkaCall(ctx, error) {
        if (!ctx) {
            return;
        }

        try {
            ctx.active_httpc_hash = false;
            var urls = ctx.httpc_host + '/' + ctx.httpc_url;
            var httpcDatas = [urls, ctx.mcallee];
            ctx.elapsed = Date.now() - ctx.start_time;

            // 에러 처리
            if (error) {
                if (!ctx.error) ctx.error = 1;
                ctx.status = 500;
                var errors = [
                    error.name || 'KafkaError',
                    error.message || 'Kafka producer error'
                ];
                AsyncSender.send_packet(PacketTypeEnum.TX_ERROR, ctx, errors);
            }

            TraceHttpc.isSlowHttpc(ctx);
            AsyncSender.send_packet(PacketTypeEnum.TX_HTTPC, ctx, httpcDatas);

        } catch (e) {
            Logger.printError('WHATAP-252', 'Kafka End Call Error', e, false);
        }
    }
};

exports.KafkaObserver = KafkaObserver;
