import mqtt from 'mqtt';
import * as utils from '../../core/utils.js';
import { createStoreServer } from './store-server.js';
import { StoreClient } from './store-client.js';

export default class LoadService {
    constructor(config) {
        this.config = config;
        this.testdata = config.loadTestData;
        if (config.resetSession) {
            this.testdata.sessionConfig.resetSession = true;
        }
        this.instances = config.instances;
        this.uuid = utils.getUUID();
        this.testdata.sessionConfig.client = { "uuid": this.uuid, "workerCount": this.instances, "state": 0, "workers": [] };
        this.mqtt = mqtt;
        this.utils = utils;
        this.client = null;
        this.workers = [];
        this.state = 0; // 0: preparing, 1: initialized, 2: starting, 3: holding, 4: running, 5: error, 6: done
        this.sessionState = 0;
        this.workerIndex = -1;
        this.syncpoint = false;
        this.storePort = config.storePort || 3333;
        this.storeClient = new StoreClient(this.storePort);
        this.storeServer = null;
    }

    async onPrepare(config, capabilities) {
        console.log('executing onPrepare() ')
        let loadService = this;

        // Start store server
        console.log(`Starting store server on port ${loadService.storePort}`);
        loadService.storeServer = createStoreServer(loadService.storePort);
        await loadService.storeClient.clear();

        console.log("Connecting to mqtt server");
        loadService.client = loadService.mqtt.connect(loadService.config.loadOrchestrator);
        loadService.client.on('connect', function () {
            loadService.client.subscribe(loadService.testdata.sessionConfig.sessionUuid + "/" + loadService.uuid);
            loadService.client.subscribe(loadService.testdata.sessionConfig.sessionUuid + "/all");
            loadService.client.publish('clientRegistrationQ', JSON.stringify(loadService.testdata.sessionConfig));
            console.log("Connected");
        });
        loadService.client.on('message', function (topic, message) {
            let messageStr = message.toString();
            console.log('Received on ', topic, ": ", messageStr);
            if (topic == loadService.testdata.sessionConfig.sessionUuid + "/all") {
                if (message == "start") {
                    // 0: preparing, 1: initialized, 2: starting, 3: holding, 4: running, 5: error, 6: done
                    loadService.state = 1;
                }
            } else if (topic == loadService.testdata.sessionConfig.sessionUuid + "/" + loadService.uuid) {
                let mess = JSON.parse(message);
                if (mess.workerStart && mess.workerEnd) {
                    let count = mess.workerStart;
                    while (count <= mess.workerEnd) {
                        loadService.workers.push({ "testIndex": count, "syncpoint": null, "state": 0 });
                        count++;
                    }
                    console.log('Initialized ', loadService.workers.length, " workers");
                }
            }
        });
        while (loadService.state < 1 || loadService.workers.length < loadService.instances) {
            await loadService.utils.delay(50);
        }
        let workerConfig = {
            "sessionUuid": loadService.testdata.sessionConfig.sessionUuid,
            "clientUuid": loadService.uuid,
            "workers": loadService.workers,
            "syncpoint": false
        }
        console.log("Registering workers");
        loadService.client.publish('workerRegistrationQ', JSON.stringify(workerConfig));
        
        // Store worker config
        await loadService.storeClient.setValue('load-workerConfig', workerConfig);
        
        loadService.client.end(true);
    }

    async beforeSession(config, capabilities, specs, cid) {
        let loadService = this;
        loadService.workerIndex = Number((cid.split('-')).pop());
        console.log(`execute beforeSession ${loadService.workerIndex}`);

        // Get worker config with retries
        let attempts = 0;
        const maxAttempts = 30;
        let workerConfig = null;
        
        while (!workerConfig && attempts < maxAttempts) {
            try {
                workerConfig = await loadService.storeClient.getValue('load-workerConfig');
                if (!workerConfig) {
                    await new Promise(resolve => setTimeout(resolve, 1000));
                    attempts++;
                    console.log(`Attempt ${attempts} to get worker config`);
                }
            } catch (error) {
                await new Promise(resolve => setTimeout(resolve, 1000));
                attempts++;
                console.log(`Attempt ${attempts} to get worker config failed: ${error.message}`);
            }
        }

        if (!workerConfig) {
            throw new Error('Failed to get worker configuration after 30 attempts');
        }

        workerConfig.workerIndex = loadService.workerIndex;
        workerConfig.testIndex = workerConfig.workers[loadService.workerIndex].testIndex;
        workerConfig.dataIndex = workerConfig.workers[loadService.workerIndex].testIndex - 1;
        global.workerConfig = workerConfig;

        loadService.uuid = workerConfig.clientUuid;
        loadService.client = loadService.mqtt.connect(loadService.config.loadOrchestrator);
        loadService.client.on('connect', function () {
            loadService.client.subscribe(loadService.testdata.sessionConfig.sessionUuid + "/" + loadService.uuid);
            loadService.client.subscribe(loadService.testdata.sessionConfig.sessionUuid + "/all");
            console.log("Connected");
        });
        loadService.client.on('message', function (topic, message) {
            if (topic == loadService.testdata.sessionConfig.sessionUuid + "/all") {
                if (message == "sync") {
                    loadService.syncpoint = false;
                }
            } else if (topic == loadService.testdata.sessionConfig.sessionUuid + "/" + loadService.uuid) {
                let mess = JSON.parse(message);
                if (mess.state) {
                    loadService.state = mess.state;
                }
            }
        });
        await loadService.utils.delay(1000);
        let stateConfig = {
            "sessionUuid": loadService.testdata.sessionConfig.sessionUuid,
            "clientUuid": loadService.uuid,
            "worker": loadService.workerIndex,
            "state": 3,
            "syncpoint": false
        };

        loadService.client.publish('updateState', JSON.stringify(stateConfig));
        while (loadService.state < 3) {
            await loadService.utils.delay(50);
        }
    }

    async afterSession() {
        if (this.client) {
            this.client.end();
        }
    }

    async onWorkerStart(cid, caps, specs, args, execArgv) {
        let loadService = this;
        let workerIndex = Number((cid.split('-')).pop());
        console.log(`execute onWorkerStart ${workerIndex}`);
        loadService.workers[workerIndex].state = 1;
        // console.log(loadService.workers);
        if (loadService.workers.filter(item => item.state == 1).length == loadService.instances) {
            // 0: preparing, 1: initialized, 2: starting, 3: holding, 4: running, 5: error, 6: done
            loadService.state = 2;
        }
        while (loadService.state < 2) {
            await loadService.utils.delay(50);
        }
    }
    onWorkerEnd() {
        // console.log('execute onWorkerEnd(cid, exitCode, specs, retries)')
    }
    async before() {
        let loadService = this;
        // console.log('execute before(commandName, args) worker: ' + loadService.workerIndex);
        let stateConfig = {
            "sessionUuid": loadService.testdata.sessionConfig.sessionUuid,
            "clientUuid": loadService.uuid,
            "worker": loadService.workerIndex,
            "state": 4,
            "syncpoint": false
        };
        loadService.client.publish('updateState', JSON.stringify(stateConfig));
        // console.log('WAITING FOR state 4 on worker: ' + loadService.workerIndex);
        while (loadService.state < 4) {
            await loadService.utils.delay(50);
        }
    }
    beforeSuite() {
        // console.log('execute beforeSuite(suite)')
    }
    beforeHook() {
        // console.log('execute beforeHook(test, context)')
    }
    afterHook() {
        // console.log('execute afterHook(test, context, { error, result, duration, passed, retries })')
    }
    async beforeTest() {
        // console.log('execute beforeTest(test, context) worker: ' + this.workerIndex);
        // console.log('execute beforeCommand(commandName, args) worker: ' + this.workerIndex);
        while (this.state < 4 || this.syncpoint) {
            await this.utils.delay(100);
        }
    }
    async beforeCommand() {
        // console.log('execute beforeCommand(commandName, args) worker: ' + this.workerIndex);
    }
    afterCommand() {
        // console.log('execute afterCommand(commandName, args, result, error)')
    }
    afterTest() {
        // console.log('execute afterTest(test, context, { error, result, duration, passed, retries })')
        if (global.workerConfig.syncpoint) {
            this.syncpoint = global.workerConfig.syncpoint;
            global.workerConfig.syncpoint = false;
            let stateConfig = {
                "sessionUuid": this.testdata.sessionConfig.sessionUuid,
                "clientUuid": this.uuid,
                "worker": this.workerIndex,
                "state": this.state,
                "syncpoint": this.syncpoint
            };
            // console.log(stateConfig);
            this.client.publish('updateState', JSON.stringify(stateConfig));
        }
    }
    afterSuite() {
        // console.log('execute afterSuite(suite)')
    }
    after() {
        // console.log('execute after(result, capabilities, specs)')
    }
    async onComplete(exitCode, config, capabilities, results) {
        console.log('execute onComplete(exitCode, config, capabilities, results)');
        if (this.storeServer) {
            this.storeServer.close(() => {
                console.log('Store server closed');
            });
        }
    }
    onReload() {
        // console.log('execute onReloadedSessionId, newSessionId)')
    }
    beforeFeature() {
        // console.log('execute beforeFeature(uri, feature, scenarios)')
    }
    beforeScenario() {
        // console.log('execute beforeScenario(uri, feature, scenario, sourceLocation, context)')
    }
    beforeStep() {
        // console.log('execute beforeStep({ uri, feature, step }, context)')
        // console.log('execute beforeStep(commandName, args) worker: ' + this.workerIndex);
        // while (this.state < 5) {
        //     await this.utils.delay(50);
        // }
    }
    afterStep() {
        // console.log('execute afterStep({ uri, feature, step }, context, { error, result, duration, passed, retries })')
    }
    afterScenario() {
        // console.log('execute afterScenario(uri, feature, scenario, result, sourceLocation, context)')
    }
    afterFeature() {
        // console.log('execute afterFeature(uri, feature, scenarios)')
    }
}