// SPDX-License-Identifier: Apache-2.0
import { expect } from 'chai';
import { afterEach, beforeEach, describe, it } from 'mocha';
import sinon from 'sinon';
import fs from 'node:fs';
import * as Base64 from 'js-base64';
import { PostgresSharedResource } from '../../../../src/core/shared-resources/postgres.js';
import { NamespaceName } from '../../../../src/types/namespace/namespace-name.js';
import { SoloError } from '../../../../src/core/errors/solo-error.js';
import * as constants from '../../../../src/core/constants.js';
import { PodReference } from '../../../../src/integration/kube/resources/pod/pod-reference.js';
import { PodName } from '../../../../src/integration/kube/resources/pod/pod-name.js';
describe('PostgresSharedResource', () => {
    const encode = (s) => Base64.encode(s);
    const namespace = NamespaceName.of('test-namespace');
    const context = 'test-context';
    let loggerStub;
    let helmStub;
    let chartManagerStub;
    let k8FactoryStub;
    let podsStub;
    let containersStub;
    let secretsStub;
    let k8Stub;
    let k8ContainerStub;
    let postgres;
    const postgresPodReference = PodReference.of(namespace, PodName.of('solo-shared-resources-postgres-0'));
    beforeEach(() => {
        helmStub = sinon.stub();
        chartManagerStub = sinon.stub();
        loggerStub = sinon.stub();
        loggerStub.info = sinon.stub();
        loggerStub.error = sinon.stub();
        k8ContainerStub = {
            copyTo: sinon.stub().resolves(),
            execContainer: sinon.stub().resolves(),
        };
        podsStub = {
            waitForRunningPhase: sinon.stub().resolves(),
            list: sinon.stub().resolves([{ podReference: postgresPodReference }]),
        };
        secretsStub = {
            list: sinon.stub().resolves([]),
            read: sinon.stub().resolves({}),
        };
        containersStub = {
            readByRef: sinon.stub().returns(k8ContainerStub),
        };
        k8Stub = {
            pods: sinon.stub().returns(podsStub),
            containers: sinon.stub().returns(containersStub),
            secrets: sinon.stub().returns(secretsStub),
        };
        k8FactoryStub = sinon.stub();
        k8FactoryStub.getK8 = sinon.stub().returns(k8Stub);
        postgres = new PostgresSharedResource(loggerStub, k8FactoryStub, helmStub, chartManagerStub);
    });
    afterEach(() => {
        sinon.restore();
    });
    describe('waitForPodReady()', () => {
        it('calls waitForRunningPhase with postgres labels', async () => {
            await postgres.waitForPodReady(namespace, context);
            expect(k8FactoryStub.getK8).to.have.been.calledWith(context);
            expect(podsStub.waitForRunningPhase).to.have.been.calledOnce;
            const [, labels] = podsStub.waitForRunningPhase.firstCall.args;
            expect(labels).to.include('app.kubernetes.io/name=postgres');
            expect(labels).to.include('app.kubernetes.io/instance=solo-shared-resources');
        });
        it('passes the namespace and configured constants for max attempts and delay', async () => {
            await postgres.waitForPodReady(namespace, context);
            const [passedNamespace, , maxAttempts, delay] = podsStub.waitForRunningPhase.firstCall.args;
            expect(passedNamespace).to.equal(namespace);
            expect(maxAttempts).to.equal(constants.PODS_RUNNING_MAX_ATTEMPTS);
            expect(delay).to.equal(constants.PODS_RUNNING_DELAY);
        });
    });
    describe('initializeMirrorNode()', () => {
        const postgresPasswordsSecret = {
            name: 'solo-shared-resources-passwords',
            data: { password: encode('superpassword') },
        };
        const mirrorPasswordsSecret = {
            data: {
                HIERO_MIRROR_IMPORTER_DB_NAME: encode('mirror_node'),
                HIERO_MIRROR_IMPORTER_DB_OWNER: encode('mirror_node_owner'),
                HIERO_MIRROR_IMPORTER_DB_OWNERPASSWORD: encode('ownerpass'),
                HIERO_MIRROR_GRAPHQL_DB_PASSWORD: encode('graphqlpass'),
                HIERO_MIRROR_GRPC_DB_PASSWORD: encode('grpcpass'),
                HIERO_MIRROR_IMPORTER_DB_PASSWORD: encode('importerpass'),
                HIERO_MIRROR_REST_DB_PASSWORD: encode('restpass'),
                HIERO_MIRROR_RESTJAVA_DB_PASSWORD: encode('restjavapass'),
                HIERO_MIRROR_ROSETTA_DB_PASSWORD: encode('rosettapass'),
                HIERO_MIRROR_WEB3_DB_PASSWORD: encode('web3pass'),
            },
        };
        let existsSyncStub;
        let mkdirSyncStub;
        let writeFileSyncStub;
        let _rmSyncStub;
        beforeEach(() => {
            secretsStub.list.resolves([postgresPasswordsSecret]);
            secretsStub.read.resolves(mirrorPasswordsSecret);
            // Simulate init script already cached so we skip the download path
            existsSyncStub = sinon.stub(fs, 'existsSync').returns(true);
            mkdirSyncStub = sinon.stub(fs, 'mkdirSync');
            writeFileSyncStub = sinon.stub(fs, 'writeFileSync');
            _rmSyncStub = sinon.stub(fs, 'rmSync');
        });
        it('reads secrets from correct labels and secret names', async () => {
            await postgres.initializeMirrorNode(namespace, context);
            expect(secretsStub.list).to.have.been.calledWith(namespace, ['app.kubernetes.io/instance=solo-shared-resources']);
            expect(secretsStub.read).to.have.been.calledWith(namespace, 'mirror-passwords');
        });
        it('copies check script, init script, and wrapper script to the postgres pod', async () => {
            await postgres.initializeMirrorNode(namespace, context);
            expect(k8ContainerStub.copyTo).to.have.been.calledTwice;
            for (const call of k8ContainerStub.copyTo.getCalls()) {
                expect(call.args[1]).to.equal('/tmp');
            }
        });
        it('executes the wrapper script inside the container', async () => {
            await postgres.initializeMirrorNode(namespace, context);
            const execCalls = k8ContainerStub.execContainer.args.map((a) => a[0]);
            expect(execCalls.some((c) => c.includes('/bin/bash /tmp/run-init.sh'))).to.be.true;
        });
        it('wrapper script contains correct DB_NAME and OWNER_USERNAME from secrets', async () => {
            await postgres.initializeMirrorNode(namespace, context);
            const wrapperArguments = writeFileSyncStub
                .getCalls()
                .find((call) => call.args[0].includes('run-init')).args;
            const writtenContent = wrapperArguments[1];
            expect(writtenContent).to.include('export DB_NAME=mirror_node');
            expect(writtenContent).to.include('export OWNER_USERNAME=mirror_node_owner');
        });
        it('wrapper script contains all required service passwords', async () => {
            await postgres.initializeMirrorNode(namespace, context);
            const wrapperArguments = writeFileSyncStub
                .getCalls()
                .find((call) => call.args[0].includes('run-init')).args;
            const writtenContent = wrapperArguments[1];
            expect(writtenContent).to.include('export GRAPHQL_PASSWORD=graphqlpass');
            expect(writtenContent).to.include('export GRPC_PASSWORD=grpcpass');
            expect(writtenContent).to.include('export IMPORTER_PASSWORD=importerpass');
            expect(writtenContent).to.include('export REST_PASSWORD=restpass');
            expect(writtenContent).to.include('export REST_JAVA_PASSWORD=restjavapass');
            expect(writtenContent).to.include('export ROSETTA_PASSWORD=rosettapass');
            expect(writtenContent).to.include('export WEB3_PASSWORD=web3pass');
        });
        it('uses a custom prefix when provided', async () => {
            const customMirrorPasswordsSecret = {
                data: {
                    CUSTOM_MIRROR_IMPORTER_DB_NAME: encode('custom_db'),
                    CUSTOM_MIRROR_IMPORTER_DB_OWNER: encode('custom_owner'),
                    CUSTOM_MIRROR_IMPORTER_DB_OWNERPASSWORD: encode('custom_ownerpass'),
                    CUSTOM_MIRROR_GRAPHQL_DB_PASSWORD: encode('graphqlpass'),
                    CUSTOM_MIRROR_GRPC_DB_PASSWORD: encode('grpcpass'),
                    CUSTOM_MIRROR_IMPORTER_DB_PASSWORD: encode('importerpass'),
                    CUSTOM_MIRROR_REST_DB_PASSWORD: encode('restpass'),
                    CUSTOM_MIRROR_RESTJAVA_DB_PASSWORD: encode('restjavapass'),
                    CUSTOM_MIRROR_ROSETTA_DB_PASSWORD: encode('rosettapass'),
                    CUSTOM_MIRROR_WEB3_DB_PASSWORD: encode('web3pass'),
                },
            };
            secretsStub.read.resolves(customMirrorPasswordsSecret);
            await postgres.initializeMirrorNode(namespace, context, 'CUSTOM');
            const wrapperArguments = writeFileSyncStub
                .getCalls()
                .find((call) => call.args[0].includes('run-init')).args;
            const writtenContent = wrapperArguments[1];
            expect(writtenContent).to.include('export DB_NAME=custom_db');
            expect(writtenContent).to.include('export OWNER_USERNAME=custom_owner');
        });
        it('uses the postgres pod from namespace zero', async () => {
            await postgres.initializeMirrorNode(namespace, context);
            const containerReference = containersStub.readByRef.firstCall.args[0];
            expect(containerReference.parentReference.name.name).to.equal(postgresPodReference.name.name);
        });
        it('throws SoloError when container copy fails', async () => {
            k8ContainerStub.copyTo.rejects(new Error('copy failed'));
            await expect(postgres.initializeMirrorNode(namespace, context)).to.be.rejectedWith(SoloError, 'Failed to copy Mirror Node Postgres initialization script to container');
        });
        it('retries execution and throws SoloError after max attempts', async () => {
            // chmod calls must succeed (they are in the outer try-catch that throws immediately).
            // Only the actual bash script execution should fail to exercise the retry loop.
            k8ContainerStub.execContainer.callsFake((cmd) => {
                if (cmd.startsWith('chmod')) {
                    return Promise.resolve();
                }
                return Promise.reject(new Error('exec failed'));
            });
            await expect(postgres.initializeMirrorNode(namespace, context)).to.be.rejectedWith(SoloError);
            expect(loggerStub.error).to.have.been.called;
        });
        it('skips downloading init script when already cached', async () => {
            // existsSync returns true — simulating cached file
            const fetchStub = sinon.stub(globalThis, 'fetch');
            await postgres.initializeMirrorNode(namespace, context);
            expect(fetchStub).to.not.have.been.called;
        });
        it('creates cache directory and downloads init script when not cached', async () => {
            const fakeStream = {
                write: sinon.stub().callsArg(1),
                end: sinon.stub(),
                on: sinon.stub(),
            };
            sinon.stub(fs, 'createWriteStream').returns(fakeStream);
            existsSyncStub.restore();
            // First call (directory check) returns false, second (file check) returns false
            existsSyncStub = sinon.stub(fs, 'existsSync');
            existsSyncStub.onFirstCall().returns(false);
            existsSyncStub.onSecondCall().returns(false);
            const mockResponse = {
                ok: true,
                body: undefined,
                arrayBuffer: sinon.stub().resolves(Buffer.from('#!/bin/bash\necho ok').buffer),
            };
            const fetchStub = sinon.stub(globalThis, 'fetch').resolves(mockResponse);
            await postgres.initializeMirrorNode(namespace, context);
            expect(mkdirSyncStub).to.have.been.calledOnce;
            expect(fetchStub).to.have.been.calledOnce;
            const fetchUrl = fetchStub.firstCall.args[0];
            expect(fetchUrl).to.include('hiero-mirror-node');
            expect(fetchUrl).to.include('init.sh');
        });
        it('throws when init script download fails', async () => {
            existsSyncStub.restore();
            existsSyncStub = sinon.stub(fs, 'existsSync');
            existsSyncStub.onFirstCall().returns(false);
            existsSyncStub.onSecondCall().returns(false);
            const mockResponse = { ok: false, status: 404, statusText: 'Not Found', body: undefined };
            sinon.stub(globalThis, 'fetch').resolves(mockResponse);
            await expect(postgres.initializeMirrorNode(namespace, context)).to.be.rejectedWith('Failed to download Mirror Node Postgres init script');
        });
    });
});
//# sourceMappingURL=postgres.test.js.map