// SPDX-License-Identifier: Apache-2.0
import { BaseCommandTest } from './base-command-test.js';
import { Flags } from '../../../../src/commands/flags.js';
import { main } from '../../../../src/index.js';
import { Duration } from '../../../../src/core/time/duration.js';
import { NamespaceName } from '../../../../src/types/namespace/namespace-name.js';
import { InjectTokens } from '../../../../src/core/dependency-injection/inject-tokens.js';
import { sleep } from '../../../../src/core/helpers.js';
import http from 'node:http';
import { expect } from 'chai';
import { container } from 'tsyringe-neo';
import { MirrorCommandDefinition } from '../../../../src/commands/command-definitions/mirror-command-definition.js';
import * as constants from '../../../../src/core/constants.js';
import fs from 'node:fs';
import { ShellRunner } from '../../../../src/core/shell-runner.js';
export class MirrorNodeTest extends BaseCommandTest {
    static soloMirrorNodeDeployArgv(testName, deployment, clusterReference, pinger) {
        const { newArgv, argvPushGlobalFlags, optionFromFlag } = MirrorNodeTest;
        const argv = newArgv();
        argv.push(MirrorCommandDefinition.COMMAND_NAME, MirrorCommandDefinition.NODE_SUBCOMMAND_NAME, MirrorCommandDefinition.NODE_ADD, optionFromFlag(Flags.deployment), deployment, optionFromFlag(Flags.clusterRef), clusterReference, optionFromFlag(Flags.enableIngress));
        if (pinger) {
            argv.push(optionFromFlag(Flags.pinger));
        }
        argvPushGlobalFlags(argv, testName, true, true);
        return argv;
    }
    static soloMirrorNodeDestroyArgv(testName, deployment, clusterReference) {
        const { newArgv, argvPushGlobalFlags, optionFromFlag } = MirrorNodeTest;
        const argv = newArgv();
        argv.push(MirrorCommandDefinition.COMMAND_NAME, MirrorCommandDefinition.NODE_SUBCOMMAND_NAME, MirrorCommandDefinition.NODE_DESTROY, optionFromFlag(Flags.deployment), deployment, optionFromFlag(Flags.clusterRef), clusterReference, optionFromFlag(Flags.force), optionFromFlag(Flags.quiet), optionFromFlag(Flags.devMode));
        argvPushGlobalFlags(argv, testName, false, true);
        return argv;
    }
    static async forwardRestServicePort(contexts, namespace) {
        const k8Factory = container.resolve(InjectTokens.K8Factory);
        const lastContext = contexts?.length ? contexts[contexts?.length - 1] : undefined;
        const k8 = k8Factory.getK8(lastContext);
        const mirrorNodeRestPods = await k8
            .pods()
            .list(namespace, ['app.kubernetes.io/name=rest', 'app.kubernetes.io/component=rest']);
        expect(mirrorNodeRestPods).to.have.lengthOf(1);
        const portForwarder = await k8
            .pods()
            .readByReference(mirrorNodeRestPods[0].podReference)
            .portForward(5551, 5551);
        await sleep(Duration.ofSeconds(2));
        return portForwarder;
    }
    static async stopPortForward(contexts, portForwarder) {
        const k8Factory = container.resolve(InjectTokens.K8Factory);
        const k8 = k8Factory.getK8(contexts[contexts.length]);
        // eslint-disable-next-line unicorn/no-null
        await k8.pods().readByReference(null).stopPortForward(portForwarder);
    }
    static async verifyMirrorNodeDeployWasSuccessful(contexts, namespace, testLogger, createdAccountIds, consensusNodesCount) {
        const portForwarder = await MirrorNodeTest.forwardRestServicePort(contexts, namespace);
        try {
            const queryUrl = 'http://localhost:5551/api/v1/network/nodes';
            let received = false;
            // wait until the transaction reached consensus and retrievable from the mirror node API
            while (!received) {
                const request = http.request(queryUrl, { method: 'GET', timeout: 100, headers: { Connection: 'close' } }, (response) => {
                    response.setEncoding('utf8');
                    response.on('data', (chunk) => {
                        // convert chunk to json object
                        const object = JSON.parse(chunk);
                        expect(object.nodes?.length, `expect there to be ${consensusNodesCount} nodes in the mirror node's copy of the address book`).to.equal(consensusNodesCount);
                        expect(object.nodes[0].service_endpoints?.length, 'expect there to be at least one service endpoint').to.be.greaterThan(0);
                        received = true;
                    });
                });
                request.on('error', (error) => {
                    testLogger.debug(`problem with request: ${error.message}`, error);
                });
                request.end(); // make the request
                await sleep(Duration.ofSeconds(2));
            }
            for (const accountId of createdAccountIds) {
                const accountQueryUrl = `http://localhost:5551/api/v1/accounts/${accountId}`;
                received = false;
                // wait until the transaction reached consensus and retrievable from the mirror node API
                while (!received) {
                    const request = http.request(accountQueryUrl, { method: 'GET', timeout: 100, headers: { Connection: 'close' } }, (response) => {
                        response.setEncoding('utf8');
                        response.on('data', (chunk) => {
                            // convert chunk to json object
                            const object = JSON.parse(chunk);
                            expect(object.account, 'expect the created account to exist in the mirror nodes copy of the accounts').to.equal(accountId);
                            received = true;
                        });
                    });
                    request.on('error', (error) => {
                        testLogger.debug(`problem with request: ${error.message}`, error);
                    });
                    request.end(); // make the request
                    await sleep(Duration.ofSeconds(2));
                }
                await sleep(Duration.ofSeconds(1));
            }
        }
        finally {
            if (portForwarder) {
                await MirrorNodeTest.stopPortForward(contexts, portForwarder);
            }
        }
    }
    static async verifyPingerStatus(contexts, namespace, pingerIsEnabled) {
        const portForwarder = await MirrorNodeTest.forwardRestServicePort(contexts, namespace);
        try {
            const transactionsEndpoint = 'http://localhost:5551/api/v1/transactions';
            // force to fetch new data instead of using cache
            const fetchOptions = {
                cache: 'no-cache',
                headers: {
                    'Cache-Control': 'no-cache, no-store, must-revalidate',
                    Pragma: 'no-cache',
                    Expires: '0',
                },
            };
            const firstResponse = await fetch(transactionsEndpoint, fetchOptions);
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const firstData = await firstResponse.json();
            console.log('\r::group::Mirror node verify pinger status first data');
            console.log(`firstData = ${JSON.stringify(firstData, null, 2)}`);
            console.log('\r::endgroup::');
            await sleep(Duration.ofSeconds(15));
            const secondResponse = await fetch(transactionsEndpoint, fetchOptions);
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const secondData = await secondResponse.json();
            console.log('\r::group::Mirror node verify pinger status second data');
            console.log(`secondData = ${JSON.stringify(secondData, null, 2)}`);
            console.log('\r::endgroup::');
            expect(firstData.transactions).to.not.be.undefined;
            expect(firstData.transactions.length).to.be.gt(0);
            expect(secondData.transactions).to.not.be.undefined;
            expect(secondData.transactions.length).to.be.gt(0);
            // if pinger is enabled, the first transaction in the first response should not equal the first transaction in the second response
            // if pinger is disabled, the first transaction in the first response should equal the first transaction in the second response
            // if there is more than one transaction in the second response, compare to the second transaction instead of the first
            let secondTransactionIndex = 0;
            if (secondData.transactions.length > 1) {
                secondTransactionIndex = 1;
            }
            if (pingerIsEnabled) {
                expect(firstData.transactions[0]).to.not.deep.equal(secondData.transactions[secondTransactionIndex]);
            }
            else {
                expect(firstData.transactions[0]).to.deep.equal(secondData.transactions[secondTransactionIndex]);
            }
        }
        finally {
            if (portForwarder) {
                await MirrorNodeTest.stopPortForward(contexts, portForwarder);
            }
        }
    }
    static add(options) {
        const { testName, testLogger, deployment, contexts, namespace, clusterReferenceNameArray, createdAccountIds, consensusNodesCount, pinger, } = options;
        const { soloMirrorNodeDeployArgv, verifyMirrorNodeDeployWasSuccessful, verifyPingerStatus } = MirrorNodeTest;
        it(`${testName}: mirror node add`, async () => {
            await main(soloMirrorNodeDeployArgv(testName, deployment, clusterReferenceNameArray[1], pinger));
            await verifyMirrorNodeDeployWasSuccessful(contexts, namespace, testLogger, createdAccountIds, consensusNodesCount);
            await verifyPingerStatus(contexts, namespace, pinger);
        }).timeout(Duration.ofMinutes(10).toMillis());
    }
    static destroy(options) {
        const { testName, deployment, clusterReferenceNameArray } = options;
        const { soloMirrorNodeDestroyArgv } = MirrorNodeTest;
        it(`${testName}: mirror node destroy`, async () => {
            await main(soloMirrorNodeDestroyArgv(testName, deployment, clusterReferenceNameArray[1]));
        }).timeout(Duration.ofMinutes(5).toMillis());
    }
    static postgresPassword = 'XXXXXXX';
    static postgresUsername = 'postgres';
    static postgresReadonlyUsername = 'readonlyuser';
    static postgresReadonlyPassword = 'XXXXXXXX';
    static postgresHostFqdn = 'my-postgresql.database.svc.cluster.local';
    static nameSpace = 'database';
    static postgresName = 'my-postgresql';
    static postgresContainerName = `${this.postgresName}-0`;
    static postgresMirrorNodeDatabaseName = 'mirror_node';
    static deployWithExternalDatabase(options) {
        const { testName, testLogger, deployment, contexts, namespace, clusterReferenceNameArray, createdAccountIds, consensusNodesCount, pinger, } = options;
        const { soloMirrorNodeDeployArgv, verifyMirrorNodeDeployWasSuccessful, verifyPingerStatus, optionFromFlag } = MirrorNodeTest;
        it(`${testName}: mirror node deploy with external database`, async () => {
            const argv = soloMirrorNodeDeployArgv(testName, deployment, clusterReferenceNameArray[1], pinger);
            process.env.USE_MIRROR_NODE_LEGACY_RELEASE_NAME = 'true';
            // Add external database flags
            argv.push(optionFromFlag(Flags.enableIngress), optionFromFlag(Flags.useExternalDatabase), optionFromFlag(Flags.externalDatabaseHost), this.postgresHostFqdn, optionFromFlag(Flags.externalDatabaseOwnerUsername), this.postgresUsername, optionFromFlag(Flags.externalDatabaseOwnerPassword), this.postgresPassword, optionFromFlag(Flags.externalDatabaseReadonlyUsername), this.postgresReadonlyUsername, optionFromFlag(Flags.externalDatabaseReadonlyPassword), this.postgresReadonlyPassword);
            await main(argv);
            await verifyMirrorNodeDeployWasSuccessful(contexts, namespace, testLogger, createdAccountIds, consensusNodesCount);
            await verifyPingerStatus(contexts, namespace, pinger);
        }).timeout(Duration.ofMinutes(10).toMillis());
        it('Enable port-forward for mirror node gRPC', async () => {
            const k8Factory = container.resolve(InjectTokens.K8Factory);
            const k8 = k8Factory.getK8(contexts[1]);
            const mirrorNodePods = await k8
                .pods()
                .list(namespace, ['app.kubernetes.io/name=grpc', 'app.kubernetes.io/component=grpc']);
            const mirrorNodePod = mirrorNodePods[0];
            await k8.pods().readByReference(mirrorNodePod.podReference).portForward(5600, 5600);
        });
    }
    static installPostgres(options) {
        const { contexts } = options;
        it('should install postgres chart', async () => {
            await new ShellRunner().run(`kubectl config use-context "${contexts[1]}"`);
            const installPostgresChartCommand = `helm repo add postgresql-helm https://leverages.github.io/helm; \
        helm install my-postgresql postgresql-helm/postgresql \
        --set deploymentType=local \
        --namespace ${this.nameSpace} --create-namespace \
        --set postgresql.auth.password=${this.postgresPassword}`;
            await new ShellRunner().run(installPostgresChartCommand);
            const k8Factory = container.resolve(InjectTokens.K8Factory);
            const k8 = k8Factory.getK8(contexts[1]);
            await k8
                .pods()
                .waitForReadyStatus(NamespaceName.of(this.nameSpace), ['app.kubernetes.io/name=postgresql'], constants.PODS_READY_MAX_ATTEMPTS, constants.PODS_READY_DELAY);
            const initScriptPath = 'scripts/external-database/init.sh';
            // check if initScriptPath exist, otherwise throw error
            if (!fs.existsSync(initScriptPath)) {
                throw new Error(`Init script not found at path: ${initScriptPath}`);
            }
            const copyInitScriptCommand = `kubectl cp ${initScriptPath} ${this.postgresContainerName}:/tmp/init.sh -n ${this.nameSpace}`;
            await new ShellRunner().run(copyInitScriptCommand);
            const chmodInitScriptCommand = `kubectl exec -it ${this.postgresContainerName} -n ${this.nameSpace} -- chmod +x /tmp/init.sh`;
            await new ShellRunner().run(chmodInitScriptCommand);
            const initScriptCommand = `kubectl exec -it ${this.postgresContainerName} -n ${this.nameSpace} -- /bin/bash /tmp/init.sh "${this.postgresUsername}" "${this.postgresReadonlyUsername}" "${this.postgresReadonlyPassword}"`;
            await new ShellRunner().run(initScriptCommand);
        }).timeout(Duration.ofMinutes(2).toMillis());
    }
    static runSql(options) {
        it('should run SQL command', async () => {
            const { testCacheDirectory } = options;
            const copySqlCommand = `kubectl cp ${testCacheDirectory}/database-seeding-query.sql ${this.postgresContainerName}:/tmp/database-seeding-query.sql -n ${this.nameSpace}`;
            await new ShellRunner().run(copySqlCommand);
            const runSqlCommand = `kubectl exec -it ${this.postgresContainerName} -n ${this.nameSpace} -- env PGPASSWORD=${this.postgresPassword} psql -U ${this.postgresUsername} -f /tmp/database-seeding-query.sql -d ${this.postgresMirrorNodeDatabaseName}`;
            await new ShellRunner().run(runSqlCommand);
        });
    }
}
//# sourceMappingURL=mirror-node-test.js.map