/**
 * 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');

const Hexa32 = require("../util/hexa32");
const SecurityMaster = require("../net/security-master");
const conf = require("../conf/configure");
const KeyGen = require("../util/keygen");
const TraceHelper = require('../util/trace-helper');
const shimmer = require('../core/shimmer');
const AsyncSender = require('../udp/async_sender');
const { PacketTypeEnum } = require("../../lib/udp");
const Transfer = require('../util/transfer');
const TraceHttpc = require('../trace/trace-httpc');
const LlmLogSinkPack = require('../llm/llm-log-sink-pack');
const llmStatCollector = require('../llm/llm-stat-collector');
const LlmIdUtil = require('../llm/llm-id-util');

var GlobalObserver = function (agent) {
    this.agent = agent;
    this.packages = ['global'];
}

var transaction_status_error_enable = conf.getProperty('transaction_status_error_enable', true);
conf.on('transaction_status_error_enable', function (newProps) {
    transaction_status_error_enable = newProps;
})

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

    shimmer.wrap(mod, 'fetch', function(original) {
        return async function(...args) {
            var info = args[1] ? args[1] : {};
            var ctx = TraceContextManager._asyncLocalStorage.getStore();

            if (ctx && (ctx.isStaticContents === true || TraceContextManager.isExist(ctx.id) === false)) {
                return original.apply(this, args);
            }

            if (ctx) {
                ctx.start_time = Date.now(); // start_time 설정 추가

                TraceHelper.interTxTraceAutoOn(ctx);

                if (conf.getProperty('mtrace_enabled', false)) {
                    addTraceHeaders(info, ctx);
                    args[1] = info;
                }

                if (args[0]) {
                    try {
                        var url;
                        if (args[0] instanceof URL) {
                            url = args[0];
                        } else if (typeof args[0] === 'string') {
                            url = new URL(args[0]);
                        } else if (args[0] instanceof Request) {
                            url = new URL(args[0].url);
                        } else if (args[0].url) {
                            // Request-like 객체
                            url = new URL(args[0].url);
                        }

                        if (url) {
                            ctx.httpc_url = url.pathname + url.search;
                            ctx.httpc_host = url.hostname;
                            ctx.httpc_port = url.port || (url.protocol === 'https:' ? 443 : 80);
                            ctx.active_httpc_hash = true;

                            try {
                                // collector key/메트릭용은 정규화된 path (query 제거).
                                // query string이 매번 달라지면 collector key 폭발 발생.
                                var fullHttpcUrl = ctx.httpc_host + url.pathname;
                                var llmOpType = LlmLogSinkPack.classifyOperation(fullHttpcUrl);
                                if (ctx._llm_httpc_pending || llmOpType !== 'unknown') {
                                    ctx.driver = 'LLM API';
                                    ctx.is_llm = 1;
                                    ctx._llm_httpc_pending = false;
                                    ctx._llm_operation_type = llmOpType;
                                    ctx._llm_httpc_url = fullHttpcUrl;
                                    ctx._llm_provider = ctx.httpc_host;
                                }
                            } catch (e) {
                            }

                            // if not ctx.step_id: ctx.step_id = random.getrandbits(63)
                            // 모든 httpc 호출에 step_id 생성
                            if (!ctx.step_id) {
                                ctx.step_id = KeyGen.next();
                            }
                        }
                    } catch (e) {
                        // URL 파싱 실패 시 무시
                    }
                }
            }

            try {
                const response = await original.apply(this, args);
                handleResponse(ctx, args, response);
                return response;
            } catch (e) {
                handleError(ctx, args, e);
                throw e;
            }
        };
    });
};

function addTraceHeaders(info, ctx) {
    // 헤더 객체 초기화
    if (!info.headers) {
        info.headers = {};
    }

    info.headers['x-wtap-po'] = Transfer.POID();

    if (conf.stat_mtrace_enabled) {
        info.headers[conf._trace_mtrace_spec_key1] = Transfer.SPEC_URL(ctx);
    }

    if (!ctx.mtid.isZero()) {
        info.headers[conf._trace_mtrace_caller_key] = Transfer.MTID_CALLERTX(ctx);
    }

    ctx.mcallee = KeyGen.getMtid();
    info.headers[conf._trace_mtrace_callee_key] = Hexa32.toString32(ctx.mcallee);
}

function handleResponse(ctx, args, response) {
    if (!ctx || !args[0]) return;

    // LLM API 호출이면 HTTP status를 collector에 누적 (llm_api_status)
    if (ctx.is_llm && response && response.status) {
        try {
            llmStatCollector.recordApiStatus(
                ctx._llm_model || 'unknown',
                ctx._llm_provider || ctx.httpc_host || '',
                ctx._llm_operation_type || 'unknown',
                ctx._llm_httpc_url || '',
                response.status
            );
        } catch (e) {}
    }

    // Error handling for 4xx, 5xx responses
    if (!response.ok && response.status >= 400 && transaction_status_error_enable) {
        let error = {
            class: response.statusText,
            message: ''
        }
        interceptorError(response.status, error, ctx);
    }

    endHttpc(ctx);
}

function handleError(ctx, args, err) {
    if (!ctx || !args[0]) return;

    const networkErrorToStatusCode = {
        'ECONNREFUSED': 503,
        'ETIMEDOUT': 504,
        'ENOTFOUND': 502,
        'ECONNRESET': 503,
        'EPIPE': 503,
        'EHOSTUNREACH': 503,
    };
    const statusCode = networkErrorToStatusCode[err.code] || 500;

    if (transaction_status_error_enable) {
        let error = {
            class: err.code || err.name,
            message: err.message || ''
        }
        interceptorError(statusCode, error, ctx);
    }

    endHttpc(ctx);
}

function interceptorError(statusCode, error, ctx) {
    if (!ctx) {
        return;
    }

    ctx.status = statusCode;
    let errors = [];
    let error_message = 'Request failed with status code ';

    if (statusCode >= 400 && !ctx.error) {
        ctx.error = 1;

        errors.push(error.class);
        if (error.message) {
            errors.push(error.message);
        } else {
            errors.push(error_message + statusCode);
        }

        AsyncSender.send_packet(PacketTypeEnum.TX_ERROR, ctx, errors);
    }
}

function endHttpc(ctx) {
    if (ctx == null) {
        return;
    }

    ctx.active_httpc_hash = false;
    let urls = `${ctx.httpc_host}:${ctx.httpc_port}${ctx.httpc_url}`;
    // datas = [ctx.httpc_url, ctx.step_id, ctx.driver]
    // 모든 httpc에 step_id 사용 (mcallee는 mtrace 외에는 Long.ZERO라 의미 없음).
    let stepIdRaw = ctx.step_id || 0;
    let stepIdStr = LlmIdUtil.stepIdStr(stepIdRaw);
    ctx._stepIdStr = stepIdStr;
    let httpcDatas = [urls, stepIdStr, ctx.driver || ''];
    ctx.elapsed = Date.now() - ctx.start_time;
    TraceHttpc.isSlowHttpc(ctx);
    AsyncSender.send_packet(PacketTypeEnum.TX_HTTPC, ctx, httpcDatas);

    // if ctx.is_llm: ctx._llm_step_id = ctx.step_id
    if (ctx.is_llm) {
        ctx._llm_step_id = ctx.step_id;
    }
    // ctx.active_httpc_hash=0; ctx.httpc_url=None; ctx.step_id=0
    ctx.driver = '';
    ctx.httpc_url = '';
    ctx.step_id = 0;
}

// interTxTraceAutoOn function moved to util/trace-helper.js

exports.GlobalObserver = GlobalObserver;