var TraceContextManager = require('../trace/trace-context-manager'),
    HashUtil = require('../util/hashutil'),
    Long = require('long'),
    Logger = require('../logger'),
    conf = require('../conf/configure'),
    DateUtil = require('../util/dateutil'),
    AsyncSender = require('../udp/async_sender'),
    PacketTypeEnum = require('../udp/packet_type_enum'),
    shimmer = require('../core/shimmer');
const { AsyncResource } = require('async_hooks');

var RedisObserver = function (agent) {
    this.agent = agent;
    this.aop = agent.aop;
    this.packages = ['redis'];
};

RedisObserver.prototype.inject = function (mod, modName) {
    if (mod.__whatap_observe__) {
        return;
    }
    mod.__whatap_observe__ = true;
    Logger.initPrint("RedisObserver");

    if (conf.sql_enabled === false) {
        return;
    }

    var self = this;
    var dbc_hash = 0;
    var dbc = 'redis://';

    // Redis 명령어 매핑 (한 번만 정의)
    var REDIS_COMMANDS = {
        // 기본 명령어들
        'ping': 'PING', 'get': 'GET', 'set': 'SET', 'del': 'DEL', 'exists': 'EXISTS', 'keys': 'KEYS',
        'setex': 'SETEX', 'setEx': 'SETEX', 'expire': 'EXPIRE', 'expireAt': 'EXPIREAT', 'expireat': 'EXPIREAT',
        'ttl': 'TTL', 'incr': 'INCR', 'decr': 'DECR', 'incrBy': 'INCRBY', 'incrby': 'INCRBY',
        'decrBy': 'DECRBY', 'decrby': 'DECRBY', 'mGet': 'MGET', 'mget': 'MGET', 'mSet': 'MSET', 'mset': 'MSET',

        // Hash 명령어들
        'hSet': 'HSET', 'hset': 'HSET', 'hGet': 'HGET', 'hget': 'HGET', 'hGetAll': 'HGETALL', 'hgetall': 'HGETALL',
        'hMGet': 'HMGET', 'hmget': 'HMGET', 'hMSet': 'HMSET', 'hmset': 'HMSET', 'hDel': 'HDEL', 'hdel': 'HDEL',

        // List 명령어들
        'lPush': 'LPUSH', 'lpush': 'LPUSH', 'rPush': 'RPUSH', 'rpush': 'RPUSH', 'lPop': 'LPOP', 'lpop': 'LPOP',
        'rPop': 'RPOP', 'rpop': 'RPOP', 'lRange': 'LRANGE', 'lrange': 'LRANGE', 'lLen': 'LLEN', 'llen': 'LLEN',
        'lSet': 'LSET', 'lset': 'LSET', 'lRem': 'LREM', 'lrem': 'LREM',

        // Set 명령어들
        'sAdd': 'SADD', 'sadd': 'SADD', 'sMembers': 'SMEMBERS', 'smembers': 'SMEMBERS', 'sRem': 'SREM', 'srem': 'SREM',
        'sCard': 'SCARD', 'scard': 'SCARD',

        // Sorted Set 명령어들
        'zAdd': 'ZADD', 'zadd': 'ZADD', 'zRange': 'ZRANGE', 'zrange': 'ZRANGE', 'zRem': 'ZREM', 'zrem': 'ZREM'
    };

    // DBC 정보 설정
    function setupDbcInfo(connectionOptions) {
        if (dbc_hash === 0 && connectionOptions) {
            dbc = 'redis://';
            dbc += connectionOptions.host || 'localhost';
            dbc += ':';
            dbc += connectionOptions.port || 6379;
            dbc += '/';
            dbc += connectionOptions.db || 0;
            dbc_hash = HashUtil.hashFromString(dbc);
        }
    }

    // 에러 처리 함수
    function handleRedisError(ctx, err) {
        if (!err) return;

        try {
            var errorClass = err.code || err.name || 'RedisError';
            var errorMessage = err.message || 'redis error';

            if (!ctx.error) ctx.error = 1;
            ctx.status = 500;
            var errors = [errorClass, errorMessage];

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

    // 통합 모니터링 함수 (한 번만 정의, 모든 명령어에서 재사용)
    function createRedisWrapper(originalFunction, commandName) {
        return function wrappedMethod() {
            var ctx = TraceContextManager.getCurrentContext();
            if (!ctx) {
                return originalFunction.apply(this, arguments);
            }

            var args = Array.prototype.slice.call(arguments);
            var callback = null;
            var hasCallback = false;

            // 콜백 함수 찾기
            if (args.length > 0 && typeof args[args.length - 1] === 'function') {
                callback = args[args.length - 1];
                hasCallback = true;
            }

            const asyncResource = new AsyncResource('redis-command');

            return asyncResource.runInAsyncScope(() => {
                // DB 연결 패킷 전송
                ctx.start_time = Date.now();
                ctx.elapsed = 0;
                ctx.active_sqlhash = true;
                AsyncSender.send_packet(PacketTypeEnum.TX_DB_CONN, ctx, [dbc]);

                // Redis 명령 시작
                var command_start_time = Date.now();
                ctx.footprint(`Redis ${commandName} Start`);

                var queryParts = [commandName];
                if (args && args.length > 0) {
                    // 콜백 함수 제외하고 실제 Redis 인자들만 처리
                    var redisArgs = hasCallback ? args.slice(0, -1) : args;
                    // 각 인자를 문자열로 변환하고 20자로 제한
                    var argStrings = redisArgs.map(function(arg) {
                        var argStr = String(arg);
                        return argStr.length > 20 ? argStr.substring(0, 20) + '...' : argStr;
                    });
                    queryParts = queryParts.concat(argStrings);
                }
                var commandText = `Redis ` + queryParts.join(' ');

                if (hasCallback) {
                    // 비동기 콜백 처리
                    const callbackResource = new AsyncResource('redis-callback');
                    args[args.length - 1] = asyncResource.bind(function(err, result) {
                        var command_elapsed = Date.now() - command_start_time;
                        var resultCount = 0;

                        if (err) {
                            handleRedisError(ctx, err);
                        } else if (result !== undefined) {
                            if (Array.isArray(result)) {
                                resultCount = result.length;
                            } else if (typeof result === 'string' || typeof result === 'number') {
                                resultCount = 1;
                            }
                        }

                        ctx.elapsed = command_elapsed;
                        ctx.active_sqlhash = false;
                        AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, commandText, String(resultCount)]);
                        ctx.footprint(`Redis ${commandName} Done`);

                        if (callback && typeof callback === 'function') {
                            return callbackResource.bind(callback).apply(this, arguments);
                        }
                    });

                    try {
                        return originalFunction.apply(this, args);
                    } catch (err) {
                        handleRedisError(ctx, err);
                        throw err;
                    }
                } else {
                    // Promise 또는 동기 처리
                    try {
                        var result = originalFunction.apply(this, args);

                        // Promise 결과 처리
                        if (result && typeof result.then === 'function') {
                            return result.then(
                                function(res) {
                                    var command_elapsed = Date.now() - command_start_time;
                                    var resultCount = 0;

                                    if (res !== undefined) {
                                        if (Array.isArray(res)) {
                                            resultCount = res.length;
                                        } else if (typeof res === 'string' || typeof res === 'number') {
                                            resultCount = 1;
                                        }
                                    }

                                    ctx.elapsed = command_elapsed;
                                    ctx.active_sqlhash = false;
                                    AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, commandText, String(resultCount)]);
                                    ctx.footprint(`Redis ${commandName} Done`);
                                    return res;
                                },
                                function(err) {
                                    handleRedisError(ctx, err);
                                    var command_elapsed = Date.now() - command_start_time;
                                    ctx.elapsed = command_elapsed;
                                    ctx.active_sqlhash = false;
                                    AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, commandText, '0']);
                                    ctx.footprint(`Redis ${commandName} Done`);
                                    throw err;
                                }
                            );
                        } else {
                            // 동기 결과 처리
                            var command_elapsed = Date.now() - command_start_time;
                            var resultCount = 0;

                            if (result !== undefined) {
                                if (Array.isArray(result)) {
                                    resultCount = result.length;
                                } else if (typeof result === 'string' || typeof result === 'number') {
                                    resultCount = 1;
                                }
                            }

                            ctx.elapsed = command_elapsed;
                            AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, commandText, String(resultCount)]);
                            ctx.footprint(`Redis ${commandName} Done`);
                            return result;
                        }
                    } catch (commandError) {
                        handleRedisError(ctx, commandError);
                        var command_elapsed = Date.now() - command_start_time;
                        ctx.elapsed = command_elapsed;
                        AsyncSender.send_packet(PacketTypeEnum.TX_SQL, ctx, [dbc, commandText, '0']);
                        ctx.footprint(`Redis ${commandName} Done`);
                        throw commandError;
                    }
                }
            });
        };
    }

    function wrapRedisMethods(client) {
        var wrappedCount = 0;
        var skippedCount = 0;
        var notFoundCount = 0;
        var alreadyWrappedCount = 0;

        // 클라이언트에 실제로 존재하는 메서드들 확인
        var availableMethods = Object.getOwnPropertyNames(client)
            .concat(Object.getOwnPropertyNames(Object.getPrototypeOf(client)))
            .filter(name => typeof client[name] === 'function' && !name.startsWith('_'))
            .sort();

        // 한 번의 루프로 모든 메서드 처리
        Object.keys(REDIS_COMMANDS).forEach(function(methodName) {

            if (!client[methodName]) {
                notFoundCount++;
                skippedCount++;
                return;
            }
            if (client[methodName].__whatap_wrapped__) {
                alreadyWrappedCount++;
                skippedCount++;
                return;
            }

            try {
                // 원본 함수 참조 저장
                var originalFunction = client[methodName];
                client[methodName] = createRedisWrapper(originalFunction, REDIS_COMMANDS[methodName]);
                client[methodName].__whatap_wrapped__ = true;
                wrappedCount++;
            } catch (e) {
                skippedCount++;
                Logger.printError('WHATAP-217', `Error wrapping ${methodName}`, e, false);
            }
        });

        return wrappedCount > 0;
    }

    // createClient 래핑
    if (mod.createClient && !mod.createClient.__whatap_wrapped__) {
        shimmer.wrap(mod, 'createClient', function(original) {
            return function wrappedCreateClient() {
                var args = Array.prototype.slice.call(arguments);
                var ctx = TraceContextManager.getCurrentContext();

                if (ctx) {
                    ctx.start_time = Date.now();
                    ctx.footprint('IORedis Client Created');
                    ctx.db_opening = true;
                }

                if (args.length > 0) {
                    var options = args[0] || {};
                    // URL에서 연결 정보 추출
                    if (typeof options === 'string') {
                        try {
                            var url = new URL(options);
                            setupDbcInfo({
                                host: url.hostname,
                                port: parseInt(url.port) || 6379,
                                db: url.pathname.slice(1) || 0
                            });
                        } catch (e) {
                            setupDbcInfo({ host: 'localhost', port: 6379, db: 0 });
                        }
                    } else if (options.url) {
                        try {
                            var url = new URL(options.url);
                            setupDbcInfo({
                                host: url.hostname,
                                port: parseInt(url.port) || 6379,
                                db: options.database || url.pathname.slice(1) || 0
                            });
                        } catch (e) {
                            setupDbcInfo(options);
                        }
                    } else {
                        setupDbcInfo(options);
                    }
                }

                var client = original.apply(this, args);

                // Redis 클라이언트 후킹
                if (client) {
                    var wrapAttempts = 0;
                    var maxAttempts = 3;
                    var wrapCompleted = false;  // 완료 플래그 추가

                    function attemptBatchWrap(reason) {
                        if (wrapCompleted) {
                            return true;
                        }

                        wrapAttempts++;

                        if (wrapRedisMethods(client)) {
                            wrapCompleted = true;  // 완료 마킹
                            return true;
                        }

                        if (wrapAttempts >= maxAttempts) {
                            return false;
                        }

                        return false;
                    }

                    // 이벤트 기반 래핑
                    if (client.on && typeof client.on === 'function') {
                        client.on('connect', function() {
                            attemptBatchWrap('Redis connect event');
                        });

                        client.on('ready', function() {
                            attemptBatchWrap('Redis ready event');
                        });
                    }

                    // 즉시 시도
                    if (!attemptBatchWrap('Immediate attempt')) {
                        // 지연 시도
                        setImmediate(function() {
                            if (!attemptBatchWrap('setImmediate attempt')) {
                                // 1초 후 마지막 시도
                                setTimeout(function() {
                                    attemptBatchWrap('Final timeout attempt');
                                }, 1000);
                            }
                        });
                    }
                }

                if (ctx) {
                    ctx.elapsed = Date.now() - ctx.start_time;
                    ctx.footprint('Redis Connecting Done');
                    ctx.db_opening = false;
                }

                return client;
            };
        });
        mod.createClient.__whatap_wrapped__ = true;
    }
};

exports.RedisObserver = RedisObserver;