/**
 * 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.
 */

const fs = require('fs');
const path = require('path');
const conf = require('../conf/configure');
const Logger = require('../logger');
const PackageCtrHelper = require('../control/packagectr-helper');

/**
 * CustomMethodObserver - Handles hook_method_patterns configuration
 * Allows users to hook custom methods in their application code
 */
const CustomMethodObserver = function(agent) {
    this.agent = agent;
    this.packages = []; // No specific packages to observe
};

/**
 * Initialize the observer - set up configuration listener
 */
CustomMethodObserver.prototype.inject = function(mod, moduleName) {
    // Configuration listener for hook_method_patterns
    conf.on('hook_method_patterns', function (value) {
        if (!value || typeof value !== 'string') {
            return;
        }

        const methods = value.split(',');

        methods.forEach(function (methodPath) {
            methodPath = methodPath.trim();

            // Parse path: ./src/user/user.service/ClassName.methodName or ./src/user/user.service/methodName
            const parts = methodPath.split('/').filter(function(p) { return p && p !== '.'; });

            if (parts.length < 1) {
                Logger.printError('WHATAP-221',
                    'Invalid hook_method_patterns format: ' + methodPath +
                    '. Expected format: ./path/to/file/ClassName.methodName or ./path/to/file/methodName',
                    null, false);
                return;
            }

            const lastPart = parts.pop();
            let methodName = null;
            let className = null;

            // Check if lastPart contains a dot (ClassName.methodName)
            if (lastPart.indexOf('.') > 0) {
                const classMethod = lastPart.split('.');
                if (classMethod.length === 2) {
                    className = classMethod[0];
                    methodName = classMethod[1];
                } else {
                    Logger.printError('WHATAP-222',
                        'Invalid class.method format: ' + lastPart +
                        '. Expected format: ClassName.methodName',
                        null, false);
                    return;
                }
            } else {
                // No dot, just a method name
                methodName = lastPart;
            }

            const relative_path = '/' + parts.join('/');

            let root = process.cwd();
            if(root.indexOf('/bin') >= 0) {
                root = root.substr(0, root.indexOf('/bin'));
            }

            // Try multiple paths for TypeScript/ESM support
            const pathsToTry = [
                root + relative_path,  // Original path
                root + relative_path.replace('/src/', '/dist/'),  // TypeScript compiled path
            ];

            // Add .js and .mjs extension variants
            const extendedPaths = [];
            pathsToTry.forEach(function(p) {
                extendedPaths.push(p);
                if (!p.endsWith('.js') && !p.endsWith('.ts') && !p.endsWith('.mjs')) {
                    extendedPaths.push(p + '.js');
                    extendedPaths.push(p + '.mjs');
                }
            });

            // Async function to handle module loading and hooking
            const tryLoadAndHook = function(pathIndex) {
                if (pathIndex >= extendedPaths.length) {
                    Logger.printError('WHATAP-223',
                        'Could not load module for hooking. Tried paths: ' + extendedPaths.join(', '),
                        null, false);
                    return;
                }

                const tryPath = extendedPaths[pathIndex];

                try {
                    if (!fs.existsSync(tryPath) && !fs.existsSync(tryPath + '.js') && !fs.existsSync(tryPath + '.mjs')) {
                        // Path doesn't exist, try next
                        tryLoadAndHook(pathIndex + 1);
                        return;
                    }

                    // addPackage now returns a Promise
                    PackageCtrHelper.addPackage(tryPath).then(function(moduleExports) {
                        if (!moduleExports) {
                            // Failed to load, try next path
                            tryLoadAndHook(pathIndex + 1);
                            return;
                        }

                        // Handle TypeScript/ES6 named exports
                        if (className) {
                            // For TypeScript classes: module exports { ClassName: [Class] }
                            if (moduleExports[className]) {
                                if (moduleExports[className].prototype && typeof moduleExports[className].prototype[methodName] === 'function') {
                                    PackageCtrHelper.hookMethod(moduleExports[className].prototype, tryPath, methodName, className);
                                } else {
                                    PackageCtrHelper.hookMethod(moduleExports[className], tryPath, methodName, className);
                                }
                                Logger.print('WHATAP-224',
                                    'Hooked class method: ' + className + '.' + methodName,
                                    false);
                            } else if (typeof moduleExports === 'function' && moduleExports.name === className) {
                                // CommonJS default export: module.exports = ExternalService
                                if (moduleExports.prototype && typeof moduleExports.prototype[methodName] === 'function') {
                                    PackageCtrHelper.hookMethod(moduleExports.prototype, tryPath, methodName, className);
                                } else {
                                    PackageCtrHelper.hookMethod(moduleExports, tryPath, methodName, className);
                                }
                                Logger.print('WHATAP-224-CJS',
                                    'Hooked CommonJS default class method: ' + className + '.' + methodName,
                                    false);
                            } else if (moduleExports.default) {
                                // ESM default export
                                if (typeof moduleExports.default === 'function' && moduleExports.default.name === className) {
                                    // Default export is the class itself
                                    if (moduleExports.default.prototype && typeof moduleExports.default.prototype[methodName] === 'function') {
                                        PackageCtrHelper.hookMethod(moduleExports.default.prototype, tryPath, methodName, className);
                                    } else {
                                        PackageCtrHelper.hookMethod(moduleExports.default, tryPath, methodName, className);
                                    }
                                    Logger.print('WHATAP-225',
                                        'Hooked ESM default class method: ' + className + '.' + methodName,
                                        false);
                                } else if (moduleExports.default[className]) {
                                    // Default export contains the class
                                    if (moduleExports.default[className].prototype && typeof moduleExports.default[className].prototype[methodName] === 'function') {
                                        PackageCtrHelper.hookMethod(moduleExports.default[className].prototype, tryPath, methodName, className);
                                    } else {
                                        PackageCtrHelper.hookMethod(moduleExports.default[className], tryPath, methodName, className);
                                    }
                                    Logger.print('WHATAP-226',
                                        'Hooked ESM class method (in default): ' + className + '.' + methodName,
                                        false);
                                } else {
                                    Logger.printError('WHATAP-227',
                                        'Class "' + className + '" not found in module exports for ' + tryPath,
                                        null, false);
                                }
                            } else {
                                Logger.printError('WHATAP-228',
                                    'Class "' + className + '" not found in module exports for ' + tryPath,
                                    null, false);
                            }
                        } else {
                            // Direct method or try to find the method in exports
                            // For ESM, check default export first
                            if (moduleExports.default && typeof moduleExports.default === 'object') {
                                if (typeof moduleExports.default[methodName] === 'function') {
                                    PackageCtrHelper.hookMethod(moduleExports.default, tryPath, methodName, null);
                                } else {
                                    PackageCtrHelper.dynamicHook(moduleExports.default, tryPath, methodName);
                                }
                                Logger.print('WHATAP-229',
                                    'Hooked ESM default export method: ' + tryPath + '/' + methodName,
                                    false);
                            } else {
                                if (typeof moduleExports[methodName] === 'function') {
                                    PackageCtrHelper.hookMethod(moduleExports, tryPath, methodName, null);
                                } else {
                                    PackageCtrHelper.dynamicHook(moduleExports, tryPath, methodName);
                                }
                                Logger.print('WHATAP-230',
                                    'Hooked method: ' + tryPath + '/' + methodName,
                                    false);
                            }
                        }
                    }).catch(function(e) {
                        // Error loading this path, try next
                        Logger.printError('WHATAP-231',
                            'Error loading module ' + tryPath + ': ' + e.message,
                            e, false);
                        tryLoadAndHook(pathIndex + 1);
                    });
                } catch (e) {
                    // Error with this path, try next
                    tryLoadAndHook(pathIndex + 1);
                }
            };

            // Start trying paths from index 0
            tryLoadAndHook(0);
        });
    });
};

module.exports.CustomMethodObserver = CustomMethodObserver;
