// SPDX-License-Identifier: Apache-2.0
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var PostgresSharedResource_1;
import { InjectTokens } from '../dependency-injection/inject-tokens.js';
import { patchInject } from '../dependency-injection/container-helper.js';
import { inject, injectable } from 'tsyringe-neo';
import { PathEx } from '../../business/utils/path-ex.js';
import { ContainerReference } from '../../integration/kube/resources/container/container-reference.js';
import { Templates } from '../templates.js';
import { ContainerName } from '../../integration/kube/resources/container/container-name.js';
import * as constants from '../../core/constants.js';
import fs, { createWriteStream } from 'node:fs';
import { SOLO_CACHE_DIR } from '../constants.js';
import { MIRROR_NODE_VERSION } from '../../../version.js';
import { PassThrough, pipeline } from 'node:stream';
import { promisify } from 'node:util';
import { SoloError } from '../errors/solo-error.js';
import * as Base64 from 'js-base64';
import { sleep } from '../helpers.js';
import { Duration } from '../time/duration.js';
import { SemanticVersion } from '../../business/utils/semantic-version.js';
let PostgresSharedResource = class PostgresSharedResource {
    static { PostgresSharedResource_1 = this; }
    logger;
    k8Factory;
    helm;
    chartManager;
    static POSTGRES_LABEL_SELECTOR = [
        'app.kubernetes.io/name=postgres',
        'app.kubernetes.io/instance=solo-shared-resources',
    ];
    constructor(logger, k8Factory, helm, chartManager) {
        this.logger = logger;
        this.k8Factory = k8Factory;
        this.helm = helm;
        this.chartManager = chartManager;
        this.helm = patchInject(helm, InjectTokens.Helm, this.constructor.name);
        this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name);
        this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name);
        this.chartManager = patchInject(chartManager, InjectTokens.ChartManager, this.constructor.name);
    }
    async waitForPodReady(namespace, context) {
        await this.k8Factory
            .getK8(context)
            .pods()
            .waitForRunningPhase(namespace, PostgresSharedResource_1.POSTGRES_LABEL_SELECTOR, constants.PODS_RUNNING_MAX_ATTEMPTS, constants.PODS_RUNNING_DELAY);
    }
    async resolveContainerReference(namespace, context) {
        const pods = this.k8Factory.getK8(context).pods();
        const matchingPods = await pods.list(namespace, PostgresSharedResource_1.POSTGRES_LABEL_SELECTOR);
        const postgresPod = matchingPods.find((pod) => Boolean(pod.podReference)) ?? matchingPods[0];
        if (postgresPod?.podReference) {
            return ContainerReference.of(postgresPod.podReference, ContainerName.of('postgresql'));
        }
        throw new SoloError(`Postgres pod not found in namespace ${namespace.name} with selector: ${PostgresSharedResource_1.POSTGRES_LABEL_SELECTOR.join(',')}`);
    }
    static tryToDecode(value) {
        return Base64.decode(value) || value;
    }
    async initializeMirrorNode(namespace, context, prefix = 'HIERO') {
        const containerReference = await this.resolveContainerReference(namespace, context);
        const k8Container = this.k8Factory.getK8(context).containers().readByRef(containerReference);
        const version = new SemanticVersion(MIRROR_NODE_VERSION.replaceAll('v', ''));
        const tag = `v${version.major}.${version.minor}.${version.patch}`;
        // check if path exists recursive PathEx.join(constants.SOLO_CACHE_DIR, 'mirror-node', mirrorRelease, 'init-script.sh')
        if (!fs.existsSync(PathEx.join(SOLO_CACHE_DIR, 'mirror-node', tag))) {
            fs.mkdirSync(PathEx.join(SOLO_CACHE_DIR, 'mirror-node', tag), { recursive: true });
        }
        const initScriptLocalPath = PathEx.join(SOLO_CACHE_DIR, 'mirror-node', tag, 'init-postgres.sh');
        // Download and cache init script
        if (!fs.existsSync(initScriptLocalPath)) {
            const initScriptDownloadUrl = Templates.renderMirrorNodeDatabaseInitScriptUrl(tag);
            this.logger.info(`Downloading Mirror Node Postgres init script from ${initScriptDownloadUrl}...`);
            const response = await fetch(initScriptDownloadUrl);
            if (!response.ok) {
                throw new Error(`Failed to download Mirror Node Postgres init script from ${initScriptDownloadUrl}: ${response.status} ${response.statusText}`);
            }
            const fileStream = createWriteStream(initScriptLocalPath);
            const streamPipeline = promisify(pipeline);
            if (response.body && typeof response.body.getReader === 'function') {
                const reader = response.body.getReader();
                try {
                    while (true) {
                        const { done, value } = await reader.read();
                        if (done) {
                            break;
                        }
                        // value is a Uint8Array chunk
                        await new Promise((resolve, reject) => {
                            fileStream.write(Buffer.from(value), (error) => (error ? reject(error) : resolve()));
                        });
                    }
                    fileStream.end();
                    await new Promise((resolve, reject) => {
                        fileStream.on('finish', resolve);
                        fileStream.on('error', reject);
                    });
                }
                finally {
                    // optional: release the lock if supported
                    reader.releaseLock?.();
                }
            }
            else if (response.body && typeof response.body.pipe === 'function') {
                await streamPipeline(response.body, fileStream);
            }
            else {
                // Fallback: load into memory and write
                const buffer = Buffer.from(await response.arrayBuffer());
                fs.writeFileSync(initScriptLocalPath, buffer);
            }
        }
        try {
            await k8Container.copyTo(initScriptLocalPath, '/tmp');
            await k8Container.execContainer('chmod +x /tmp/init-postgres.sh');
        }
        catch (error) {
            throw new SoloError(`Failed to copy Mirror Node Postgres initialization script to container: ${error.message}`, error);
        }
        const sharedResourcesSecrets = await this.k8Factory
            .getK8(context)
            .secrets()
            .list(namespace, ['app.kubernetes.io/instance=solo-shared-resources']);
        const postgresPasswordsSecret = sharedResourcesSecrets.find((secret) => secret.name === 'solo-shared-resources-passwords');
        const mirrorPasswordsSecret = await this.k8Factory
            .getK8(context)
            .secrets()
            .read(namespace, 'mirror-passwords');
        const superUserPassword = Base64.decode(postgresPasswordsSecret.data['password']);
        const databaseName = Base64.decode(mirrorPasswordsSecret.data[`${prefix}_MIRROR_IMPORTER_DB_NAME`]);
        const ownerUsername = Base64.decode(mirrorPasswordsSecret.data[`${prefix}_MIRROR_IMPORTER_DB_OWNER`]);
        const ownerPassword = Base64.decode(mirrorPasswordsSecret.data[`${prefix}_MIRROR_IMPORTER_DB_OWNERPASSWORD`]);
        const maxAttempts = 3;
        const backoff = 2;
        let attempt = 1;
        while (attempt < maxAttempts) {
            try {
                const wrapperScriptName = 'run-init.sh';
                const wrapperLines = [
                    '#!/usr/bin/env bash',
                    'set -e',
                    '',
                    '# connection and DB vars',
                    'export POSTGRES_USER=postgres',
                    'export PGUSER=postgres',
                    'export PGDATABASE=postgres',
                    'export PGHOST=127.0.0.1',
                    'export PGPORT=5432',
                    `export DB_NAME=${databaseName}`,
                    `export OWNER_USERNAME=${ownerUsername}`,
                    `export OWNER_PASSWORD=${ownerPassword}`,
                    '',
                    '# superuser password (from your secrets list)',
                    `export SUPERUSER_PASSWORD=${superUserPassword}`,
                    '',
                    '# build .pgpass with both postgres (superuser) and owner credentials',
                    'cat > /tmp/.pgpass <<EOF',
                    `127.0.0.1:5432:*:postgres:${superUserPassword}`,
                    `127.0.0.1:5432:${databaseName}:${ownerUsername}:${ownerPassword}`,
                    'EOF',
                    'chmod 600 /tmp/.pgpass',
                    'export PGPASSFILE=/tmp/.pgpass',
                    '',
                    '',
                    '# export the other API user passwords used by init script',
                    `export GRAPHQL_PASSWORD=${PostgresSharedResource_1.tryToDecode(mirrorPasswordsSecret.data[`${prefix}_MIRROR_GRAPHQL_DB_PASSWORD`])}`,
                    `export GRPC_PASSWORD=${PostgresSharedResource_1.tryToDecode(mirrorPasswordsSecret.data[`${prefix}_MIRROR_GRPC_DB_PASSWORD`])}`,
                    `export IMPORTER_PASSWORD=${PostgresSharedResource_1.tryToDecode(mirrorPasswordsSecret.data[`${prefix}_MIRROR_IMPORTER_DB_PASSWORD`])}`,
                    `export REST_PASSWORD=${PostgresSharedResource_1.tryToDecode(mirrorPasswordsSecret.data[`${prefix}_MIRROR_REST_DB_PASSWORD`])}`,
                    `export REST_JAVA_PASSWORD=${PostgresSharedResource_1.tryToDecode(mirrorPasswordsSecret.data[`${prefix}_MIRROR_RESTJAVA_DB_PASSWORD`])}`,
                    `export ROSETTA_PASSWORD=${PostgresSharedResource_1.tryToDecode(mirrorPasswordsSecret.data[`${prefix}_MIRROR_ROSETTA_DB_PASSWORD`])}`,
                    `export WEB3_PASSWORD=${PostgresSharedResource_1.tryToDecode(mirrorPasswordsSecret.data[`${prefix}_MIRROR_WEB3_DB_PASSWORD`])}`,
                    '',
                    'exec /bin/bash /tmp/init-postgres.sh',
                ];
                const wrapper = wrapperLines.join('\n');
                const temporaryLocal = PathEx.join(constants.SOLO_CACHE_DIR, wrapperScriptName);
                fs.writeFileSync(temporaryLocal, wrapper);
                await k8Container.copyTo(temporaryLocal, '/tmp');
                await k8Container.execContainer(`chmod +x /tmp/${wrapperScriptName}`);
                const outputStream = new PassThrough();
                outputStream.on('data', (chunk) => {
                    this.logger.info(`${wrapperScriptName}: ${chunk.toString()}`);
                });
                const errorStream = new PassThrough();
                errorStream.on('data', (chunk) => {
                    this.logger.info(`${wrapperScriptName}: ${chunk.toString()}`);
                });
                await k8Container.execContainer(`/bin/bash /tmp/${wrapperScriptName}`, outputStream, errorStream);
                await k8Container.execContainer('rm /tmp/.pgpass');
                await k8Container.execContainer(`rm /tmp/${wrapperScriptName}`);
                fs.rmSync(temporaryLocal);
                break;
            }
            catch (error) {
                this.logger.error(`Failed to run Mirror Node Postgres initialization script in container. Attempt ${attempt} out of ${maxAttempts}: ${error}`);
                attempt++;
                if (attempt >= maxAttempts) {
                    throw new SoloError(`Failed to run Mirror Node Postgres initialization script in container after ${attempt} attempts: ${error}`, error);
                }
                await sleep(Duration.ofSeconds(backoff * attempt)); // wait before retrying
            }
        }
    }
};
PostgresSharedResource = PostgresSharedResource_1 = __decorate([
    injectable(),
    __param(0, inject(InjectTokens.SoloLogger)),
    __param(1, inject(InjectTokens.K8Factory)),
    __param(2, inject(InjectTokens.Helm)),
    __param(3, inject(InjectTokens.ChartManager)),
    __metadata("design:paramtypes", [Object, Object, Object, Function])
], PostgresSharedResource);
export { PostgresSharedResource };
//# sourceMappingURL=postgres.js.map