// METADATA // {"ai-commented":{"service":"claude"}}
/*
 * Copyright (C) 2024-present Puter Technologies Inc.
 *
 * This file is part of Puter.
 *
 * Puter is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
import { AdvancedBase } from '@heyputer/putility';
import useapi from 'useapi';
import why from '../exports.js';
import { RuntimeModuleRegistry } from '../src/extension/RuntimeModuleRegistry.js';
import { Kernel } from '../src/Kernel.js';
import { Core2Module } from '../src/modules/core/Core2Module.js';
import { Container } from '../src/services/Container.js';
import { HTTPThumbnailService } from '../src/services/thumbnails/HTTPThumbnailService.js';
import { consoleLogManager } from '../src/util/consolelog.js';
import { Context } from '../src/util/context.js';
import { TestCoreModule } from '../src/modules/test-core/TestCoreModule.js';
const { BaseService, EssentialModules } = why;

/**
 * A simple implementation of the log interface for the test kernel.
 */
class TestLogger {
    constructor () {
        console.log('\x1B[36;1mBoot logger started :)\x1B[0m');
    }
    info (...args) {
        console.log('\x1B[36;1m[TESTKERNEL/INFO]\x1B[0m',
                        ...args);
    }
    error (...args) {
        console.log('\x1B[31;1m[TESTKERNEL/ERROR]\x1B[0m',
                        ...args);
    }
}

/**
* TestKernel class extends AdvancedBase to provide a testing environment for Puter services
* Implements a simplified version of the main Kernel for testing purposes, including:
* - Module management and installation
* - Service container initialization
* - Custom logging functionality
* - Context creation and management
* Does not include full service initialization or legacy service support
*/
export class TestKernel extends AdvancedBase {

    /**@type {Context} */
    root_context;
    constructor () {
        super();

        this.modules = [];
        this.useapi = useapi();

        /**
        * Initializes the useapi instance for the test kernel.
        * Defines base Module and Service classes in the useapi context.
        * @returns {void}
        */
        this.useapi.withuse(() => {
            // eslint-disable-next-line no-undef
            def('Module', AdvancedBase);
            // eslint-disable-next-line no-undef
            def('Service', BaseService);
        });

        this.logfn_ = (...a) => a;

        this.runtimeModuleRegistry = new RuntimeModuleRegistry();
    }

    add_module (module) {
        this.modules.push(module);
    }

    /**
    * Adds a module to the test kernel's module list
    * @param {Module} module - The module instance to add
    * @description Stores the provided module in the kernel's internal modules array for later installation
    */
    boot () {
        consoleLogManager.initialize_proxy_methods();

        consoleLogManager.decorate_all(({ _manager, replace }, ...a) => {
            replace(...this.logfn_(...a));
        });

        this.testLogger = new TestLogger();

        const services = new Container({ logger: this.testLogger });
        this.services = services;
        // app.set('services', services);

        const root_context = Context.create({
            services,
            useapi: this.useapi,
            ['runtime-modules']: this.runtimeModuleRegistry,
            args: {},
        }, 'app');
        this.root_context = root_context;
        globalThis.root_context = root_context;

        root_context.arun(async () => {
            await this._install_modules();
            // await this._boot_services();
        });

        // Error.stackTraceLimit = Infinity;
        Error.stackTraceLimit = 200;
    }

    /**
    * Installs modules into the test kernel environment
    */
    async _install_modules () {
        const { services } = this;

        const mod_install_root_context = Context.get();

        for ( const module of this.modules ) {
            try {
                const mod_context = this._create_mod_context(mod_install_root_context,
                                {
                                    name: module.constructor.name,
                                    ['module']: module,
                                    external: false,
                                });
                await this.root_context.arun(async () => {
                    await module.install(mod_context);
                });
            } catch (e) {
                console.log(e);
                throw e;
            }
        }

        // Real kernel initializes services here, but in this test kernel
        // we don't initialize any services.

        // Real kernel adds legacy services here but these will break
        // the test kernel.

        services.ready.resolve();

        // provide services to helpers
        // const { tmp_provide_services } = require('../src/helpers');
        // tmp_provide_services(services);
    }
}

TestKernel.prototype._create_mod_context =
    Kernel.prototype._create_mod_context;

const do_after_tests_ = [];

/**
* Executes a function immediately and adds it to the list of functions to be executed after tests
*
* This is used to log things inline with console output from tests, and then
* again later without those console outputs.
*
* @param {Function} fn - The function to execute and store for later
*/
const repeat_after = (fn) => {
    fn();
    do_after_tests_.push(fn);
};

let total_passed = 0;
let total_failed = 0;

/**
* Tracks test results across all services
* @type {number} total_passed - Count of all passed assertions
* @type {number} total_failed - Count of all failed assertions
*/
const main = async () => {
    const k = new TestKernel();
    for ( const mod of EssentialModules ) {
        k.add_module(new mod());
    }
    k.add_module({
        install: async (context) => {
            const services = context.get('services');
            services.registerService('thumbs-http', HTTPThumbnailService);
        },
    });
    k.boot();
    console.log('awaiting services ready');
    await k.services.ready;
    console.log('services have become ready');

    const service_names = process.argv.length > 2
        ? process.argv.slice(2)
        : Object.keys(k.services.instances_);

    for ( const name of service_names ) {
        if ( ! k.services.instances_[name] ) {
            console.log(`\x1B[31;1mService not found: ${name}\x1B[0m`);
            process.exit(1);
        }

        const ins = k.services.instances_[name];
        ins.construct();
        if ( !ins._test || typeof ins._test !== 'function' ) {
            continue;
        }
        ins.log = k.testLogger;
        let passed = 0;
        let failed = 0;

        repeat_after(() => {
            console.log(`\x1B[33;1m=== [ Service :: ${name} ] ===\x1B[0m`);
        });

        const testapi = {
            assert: (condition, name) => {
                name = name || condition.toString();
                if ( condition() ) {
                    passed++;
                    repeat_after(() => console.log(`\x1B[32;1m  ✔ ${name}\x1B[0m`));
                } else {
                    failed++;
                    repeat_after(() => console.log(`\x1B[31;1m  ✘ ${name}\x1B[0m`));
                }
            },
        };

        testapi.assert.equal = (a, b, name) => {
            name = name || `${a} === ${b}`;
            if ( a === b ) {
                passed++;
                repeat_after(() => console.log(`\x1B[32;1m  ✔ ${name}\x1B[0m`));
            } else {
                failed++;
                repeat_after(() => {
                    console.log(`\x1B[31;1m  ✘ ${name}\x1B[0m`);
                    console.log(`\x1B[31;1m    Expected: ${b}\x1B[0m`);
                    console.log(`\x1B[31;1m    Got: ${a}\x1B[0m`);
                });
            }
        };

        await ins._test(testapi);

        total_passed += passed;
        total_failed += failed;
    }

    console.log('\x1B[36;1m<===\x1B[0m ' +
        'ASSERTION OUTPUTS ARE REPEATED BELOW' +
        ' \x1B[36;1m===>\x1B[0m');

    for ( const fn of do_after_tests_ ) {
        fn();
    }

    console.log('\x1B[36;1m=== [ Summary ] ===\x1B[0m');
    console.log(`Passed: ${total_passed}`);
    console.log(`Failed: ${total_failed}`);

    process.exit(total_failed ? 1 : 0);
};

if ( import.meta.main ) {
    main();
}

export const createTestKernel = async ({
    serviceMap = {},
    initLevelString = 'construct',
    extraSteps = true,
    testCore = false,
    serviceConfigOverrideMap = {},
    globalConfigOverrideMap = {},
    serviceMapArgs = {},
}) => {

    const initLevelMap = { CONSTRUCT: 1, INIT: 2 };
    const initLevel = initLevelMap[(`${initLevelString}`).toUpperCase()];
    const testKernel = new TestKernel();
    testKernel.add_module(new Core2Module());
    if ( testCore ) testKernel.add_module(new TestCoreModule());
    for ( const [name, service] of Object.entries(serviceMap) ) {
        testKernel.add_module({
            install: context => {
                const services = context.get('services');
                services.registerService(name, service, serviceMapArgs[name] || undefined);
            },
        });
    }
    testKernel.boot();
    await testKernel.services.ready;
    const service_names = Object.keys(testKernel.services.instances_);

    for ( const name of service_names ) {

        const serviceConfigOverride = serviceConfigOverrideMap[name] ;
        const globalConfigOverride = globalConfigOverrideMap[name] ;

        if ( serviceConfigOverride ) {
            const ins = testKernel.services.instances_[name];
            // Apply service config overrides
            ins.config = {
                ...ins.config,
                ...serviceConfigOverride,
            };
        }

        if ( globalConfigOverride ) {
            const ins = testKernel.services.instances_[name];
            // Apply global config overrides
            ins.global_config = {
                ...ins.global_config,
                ...globalConfigOverride,
            };
        }
    }

    for ( const name of service_names ) {
        const ins = testKernel.services.instances_[name];
        // Fix context
        ins.context = testKernel.root_context;
        if ( initLevel >= initLevelMap.CONSTRUCT ) {
            await ins.construct();
        }
    }
    for ( const name of service_names ) {
        const ins = testKernel.services.instances_[name];
        if ( initLevel >= initLevelMap.INIT ) {
            await ins.init();
        }
    }
    if ( extraSteps && testCore && initLevel >= initLevelMap.INIT ) {
        await testKernel.services?.get('su').__on('boot.consolidation', []);
    }
    return testKernel;
};
