// SPDX-License-Identifier: Apache-2.0
import * as x509 from '@peculiar/x509';
import { DataValidationError } from './errors/data-validation-error.js';
import { IllegalArgumentError } from './errors/illegal-argument-error.js';
import { MissingArgumentError } from './errors/missing-argument-error.js';
import { SoloError } from './errors/solo-error.js';
import * as constants from './constants.js';
import { PodName } from '../integration/kube/resources/pod/pod-name.js';
import { GrpcProxyTlsEnums } from './enumerations.js';
import { PathEx } from '../business/utils/path-ex.js';
import { HEDERA_PLATFORM_VERSION } from '../../version.js';
import { OperatingSystem } from '../business/utils/operating-system.js';
export class Templates {
    static renderNetworkPodName(nodeAlias) {
        return PodName.of(`network-${nodeAlias}-0`);
    }
    static renderNetworkSvcName(nodeAlias) {
        return `network-${nodeAlias}-svc`;
    }
    static renderNetworkHeadlessSvcName(nodeAlias) {
        return `network-${nodeAlias}`;
    }
    static renderNodeAliasFromNumber(number_) {
        return `node${number_}`;
    }
    static renderPostgresPodName(number_) {
        return PodName.of(`solo-shared-resources-postgres-${number_}`);
    }
    static renderNodeAliasesFromCount(count, existingNodesCount) {
        const nodeAliases = [];
        let nodeNumber = existingNodesCount + 1;
        for (let index = 1; index <= count; index++) {
            nodeAliases.push(Templates.renderNodeAliasFromNumber(nodeNumber));
            nodeNumber++;
        }
        return nodeAliases;
    }
    static renderMirrorNodeDatabaseInitScriptUrl(release) {
        return `https://raw.githubusercontent.com/hiero-ledger/hiero-mirror-node/refs/tags/${release}/importer/src/main/resources/db/scripts/init.sh`;
    }
    static renderGossipPemPrivateKeyFile(nodeAlias) {
        return `${constants.SIGNING_KEY_PREFIX}-private-${nodeAlias}.pem`;
    }
    static renderGossipPemPublicKeyFile(nodeAlias) {
        return `${constants.SIGNING_KEY_PREFIX}-public-${nodeAlias}.pem`;
    }
    static renderTLSPemPrivateKeyFile(nodeAlias) {
        return `hedera-${nodeAlias}.key`;
    }
    static renderTLSPemPublicKeyFile(nodeAlias) {
        return `hedera-${nodeAlias}.crt`;
    }
    static renderNodeAdminKeyName(nodeAlias) {
        return `${nodeAlias}-admin`;
    }
    static renderNodeFriendlyName(prefix, nodeAlias, suffix = '') {
        const parts = [prefix, nodeAlias];
        if (suffix) {
            parts.push(suffix);
        }
        return parts.join('-');
    }
    static extractNodeAliasFromPodName(podName) {
        const parts = podName.name.split('-');
        if (parts.length !== 3) {
            throw new DataValidationError(`pod name is malformed : ${podName.name}`, 3, parts.length);
        }
        return parts[1].trim();
    }
    static prepareReleasePrefix(tag) {
        if (!tag) {
            throw new MissingArgumentError('tag cannot be empty');
        }
        const parsed = tag.split('.');
        if (parsed.length < 3) {
            throw new Error(`tag (${tag}) must include major, minor and patch fields (e.g. v0.40.4)`);
        }
        return `${parsed[0]}.${parsed[1]}`;
    }
    /**
     * renders the name to be used to store the new account key as a Kubernetes secret
     * @param accountId
     * @returns the name of the Kubernetes secret to store the account key
     */
    static renderAccountKeySecretName(accountId) {
        return `account-key-${accountId.toString()}`;
    }
    /**
     * renders the label selector to be used to fetch the new account key from the Kubernetes secret
     * @param accountId
     * @returns the label selector of the Kubernetes secret to retrieve the account key   */
    static renderAccountKeySecretLabelSelector(accountId) {
        return `solo.hedera.com/account-id=${accountId.toString()}`;
    }
    /**
     * renders the label object to be used to store the new account key in the Kubernetes secret
     * @param accountId
     * @returns the label object to be used to store the new account key in the Kubernetes secret
     */
    static renderAccountKeySecretLabelObject(accountId) {
        return {
            'solo.hedera.com/account-id': accountId.toString(),
        };
    }
    static renderDistinguishedName(nodeAlias, state = 'TX', locality = 'Richardson', org = 'Hedera', orgUnit = 'Hedera', country = 'US') {
        return new x509.Name(`CN=${nodeAlias},ST=${state},L=${locality},O=${org},OU=${orgUnit},C=${country}`);
    }
    static renderStagingDir(cacheDirectory, releaseTagOverride) {
        let releaseTag = releaseTagOverride;
        if (!cacheDirectory) {
            throw new IllegalArgumentError('cacheDirectory cannot be empty');
        }
        if (!releaseTag) {
            releaseTag = HEDERA_PLATFORM_VERSION;
        }
        const releasePrefix = this.prepareReleasePrefix(releaseTag);
        if (!releasePrefix) {
            throw new IllegalArgumentError('releasePrefix cannot be empty');
        }
        return PathEx.resolve(PathEx.join(cacheDirectory, releasePrefix, 'staging', releaseTag));
    }
    static localInstallationExecutableForDependency(dependency, installationDirectory = PathEx.join(constants.SOLO_HOME_DIR, 'bin')) {
        switch (dependency) {
            case constants.HELM:
            case constants.KIND:
            case constants.PODMAN:
            case constants.VFKIT:
            case constants.GVPROXY:
            case constants.KUBECTL: {
                if (OperatingSystem.isWin32()) {
                    return PathEx.join(installationDirectory, `${dependency}.exe`);
                }
                return PathEx.join(installationDirectory, dependency);
            }
            default: {
                throw new SoloError(`unknown dependency: ${dependency}`);
            }
        }
    }
    static renderFullyQualifiedNetworkPodName(namespace, nodeAlias) {
        return `${Templates.renderNetworkPodName(nodeAlias)}.${Templates.renderNetworkHeadlessSvcName(nodeAlias)}.${namespace.name}.svc.cluster.local`;
    }
    static renderFullyQualifiedNetworkSvcName(namespace, nodeAlias) {
        return `${Templates.renderNetworkSvcName(nodeAlias)}.${namespace.name}.svc.cluster.local`;
    }
    static nodeIdFromNodeAlias(nodeAlias) {
        for (let index = nodeAlias.length - 1; index > 0; index--) {
            if (Number.isNaN(Number.parseInt(nodeAlias[index]))) {
                return Number.parseInt(nodeAlias.slice(index + 1)) - 1;
            }
        }
        throw new SoloError(`Can't get node id from node ${nodeAlias}`);
    }
    static renderComponentIdFromNodeId(nodeId) {
        return nodeId + 1;
    }
    static renderComponentIdFromNodeAlias(nodeAlias) {
        return this.nodeIdFromNodeAlias(nodeAlias) + 1;
    }
    static renderNodeIdFromComponentId(componentId) {
        return componentId - 1;
    }
    static renderGossipKeySecretName(nodeAlias) {
        return `network-${nodeAlias}-keys-secrets`;
    }
    static renderGossipKeySecretLabelObject(nodeAlias) {
        return { 'solo.hedera.com/node-name': nodeAlias };
    }
    /**
     * Creates the secret name based on the node alias type
     *
     * @param nodeAlias - node alias
     * @param type - whether is for gRPC or gRPC Web ( Haproxy or Envoy )
     *
     * @returns the appropriate secret name
     */
    static renderGrpcTlsCertificatesSecretName(nodeAlias, type) {
        switch (type) {
            //? HAProxy Proxy
            case GrpcProxyTlsEnums.GRPC: {
                return `haproxy-proxy-secret-${nodeAlias}`;
            }
            //? Envoy Proxy
            case GrpcProxyTlsEnums.GRPC_WEB: {
                return `envoy-proxy-secret-${nodeAlias}`;
            }
        }
    }
    /**
     * Creates the secret labels based on the node alias type
     *
     * @param nodeAlias - node alias
     * @param type - whether is for gRPC or gRPC Web ( Haproxy or Envoy )
     *
     * @returns the appropriate secret labels
     */
    static renderGrpcTlsCertificatesSecretLabelObject(nodeAlias, type) {
        switch (type) {
            //? HAProxy Proxy
            case GrpcProxyTlsEnums.GRPC: {
                return { 'haproxy-proxy-secret': nodeAlias };
            }
            //? Envoy Proxy
            case GrpcProxyTlsEnums.GRPC_WEB: {
                return { 'envoy-proxy-secret': nodeAlias };
            }
        }
    }
    static parseNodeAliasToIpMapping(unparsed) {
        const mapping = {};
        for (const data of unparsed.split(',')) {
            const [nodeAlias, ip] = data.split('=');
            mapping[nodeAlias] = ip;
        }
        return mapping;
    }
    /**
     * Parses a comma-separated string into a mapping of node aliases → address/port.
     *
     * Accepted input formats:
     * 1) Explicit alias → address[:port]
     *    Each entry provides the node alias and the target address, optionally with a port.
     *    Example: "node1=127.0.0.1:8080,node2=127.0.0.1:8081"
     *
     * 2) Explicit alias → address (no port)
     *    Same as above, but if the port is omitted it defaults to 8080.
     *    Example: "node1=localhost,node2=localhost:8081"
     *
     * 3) Address[:port] only (no aliases)
     *    Aliases are inferred from the `nodes` array by index order.
     *    If the port is omitted, it defaults to 8080.
     *    Example: "localhost,127.0.0.2:8081"
     *
     * @param unparsed - Input string describing alias/address[:port] mappings.
     * @param nodes - Used to infer aliases when not explicitly provided.
     * @returns Record keyed by NodeAlias with resolved address and port.
     *
     * @throws SoloError if an alias cannot be inferred.
     */
    static parseNodeAliasToAddressAndPortMapping(unparsed, nodes) {
        const mapping = {};
        if (!unparsed || typeof unparsed !== 'string') {
            return mapping;
        }
        for (const [index, data] of unparsed.split(',').entries()) {
            const [nodeAlias, addressData] = data.includes('=')
                ? data.split('=')
                : [nodes[index]?.name, data];
            if (!nodeAlias) {
                throw new SoloError(`Node alias for ${addressData} cannot be inferred`);
            }
            const [address, port] = addressData.includes(':') ? addressData.split(':') : [addressData, '8080'];
            mapping[nodeAlias] = { address, port: +port };
        }
        return mapping;
    }
    static parseNodeAliasToDomainNameMapping(unparsed) {
        const mapping = {};
        for (const data of unparsed.split(',')) {
            const [nodeAlias, domainName] = data.split('=');
            if (!nodeAlias || typeof nodeAlias !== 'string') {
                throw new SoloError(`Can't parse node alias: ${data}`);
            }
            if (!domainName || typeof domainName !== 'string') {
                throw new SoloError(`Can't parse domain name: ${data}`);
            }
            mapping[nodeAlias] = domainName;
        }
        return mapping;
    }
    /**
     * Renders the fully qualified domain name for a consensus node. We support the following variables for templating
     * in the dnsConsensusNodePattern: {nodeAlias}, {nodeId}, {namespace}, {cluster}
     *
     * The end result will be `${dnsConsensusNodePattern}.${dnsBaseDomain}`.
     * For example, if the dnsConsensusNodePattern is `network-{nodeAlias}-svc.{namespace}.svc` and the dnsBaseDomain is `cluster.local`,
     * the fully qualified domain name will be `network-{nodeAlias}-svc.{namespace}.svc.cluster.local`.
     * @param nodeAlias - the alias of the consensus node
     * @param nodeId - the id of the consensus node
     * @param namespace - the namespace of the consensus node
     * @param cluster - the cluster of the consensus node
     * @param dnsBaseDomain - the base domain of the cluster
     * @param dnsConsensusNodePattern - the pattern to use for the consensus node
     */
    static renderConsensusNodeFullyQualifiedDomainName(nodeAlias, nodeId, namespace, cluster, dnsBaseDomain, dnsConsensusNodePattern) {
        const searchReplace = {
            '{nodeAlias}': nodeAlias,
            '{nodeId}': nodeId.toString(),
            '{namespace}': namespace,
            '{cluster}': cluster,
        };
        for (const [search, replace] of Object.entries(searchReplace)) {
            dnsConsensusNodePattern = dnsConsensusNodePattern.replace(search, replace);
        }
        return `${dnsConsensusNodePattern}.${dnsBaseDomain}`;
    }
    /**
     * @param serviceName - name of the service
     * @param namespace - the pattern to use for the consensus node
     * @param dnsBaseDomain - the base domain of the cluster
     */
    static renderSvcFullyQualifiedDomainName(serviceName, namespace, dnsBaseDomain) {
        return `${serviceName}.${namespace}.svc.${dnsBaseDomain}`;
    }
    // Component Label Selectors
    static renderRelayLabels(id, legacyReleaseName) {
        return legacyReleaseName
            ? [`app.kubernetes.io/instance=${legacyReleaseName}`, 'app.kubernetes.io/name=relay']
            : [`app.kubernetes.io/instance=${constants.JSON_RPC_RELAY_RELEASE_NAME}-${id}`, 'app.kubernetes.io/name=relay'];
    }
    static renderHaProxyLabels(id) {
        const nodeAlias = Templates.renderNodeAliasFromNumber(id);
        return [`app=haproxy-${nodeAlias}`, 'solo.hedera.com/type=haproxy'];
    }
    static renderMirrorNodeLabels(id, legacyReleaseName) {
        const releaseName = legacyReleaseName ?? Templates.renderMirrorNodeName(id);
        return [
            'app.kubernetes.io/name=importer',
            'app.kubernetes.io/component=importer',
            `app.kubernetes.io/instance=${releaseName}`,
        ];
    }
    static renderMirrorIngressControllerLabels() {
        return [constants.SOLO_INGRESS_CONTROLLER_NAME_LABEL];
    }
    static renderEnvoyProxyLabels(id) {
        const nodeAlias = Templates.renderNodeAliasFromNumber(id);
        return [`solo.hedera.com/node-name=${nodeAlias}`, 'solo.hedera.com/type=envoy-proxy'];
    }
    static renderExplorerLabels(id, legacyReleaseName) {
        const releaseName = legacyReleaseName ?? `${constants.EXPLORER_RELEASE_NAME}-${id}`;
        return [`app.kubernetes.io/instance=${releaseName}`];
    }
    static renderConsensusNodeLabels(id) {
        return [`app=network-${Templates.renderNodeAliasFromNumber(id)}`];
    }
    static renderBlockNodeLabels(id, legacyReleaseName) {
        const releaseName = legacyReleaseName ?? Templates.renderBlockNodeName(id);
        return [`app.kubernetes.io/name=${releaseName}`];
    }
    static renderExplorerName(id) {
        return `${constants.EXPLORER_RELEASE_NAME}-${id}`;
    }
    static renderRelayName(id) {
        return `${constants.JSON_RPC_RELAY_RELEASE_NAME}-${id}`;
    }
    static renderBlockNodeName(id) {
        return `${constants.BLOCK_NODE_RELEASE_NAME}-${id}`;
    }
    static renderMirrorNodeName(id) {
        return `${constants.MIRROR_NODE_RELEASE_NAME}-${id}`;
    }
    static renderConfigMapRemoteConfigLabels() {
        return ['solo.hedera.com/type=remote-config'];
    }
    static renderNodeLabelsFromNodeAlias(nodeAlias) {
        return [`solo.hedera.com/node-name=${nodeAlias}`, 'solo.hedera.com/type=network-node'];
    }
    static renderNodeSvcLabelsFromNodeId(nodeId) {
        return [`solo.hedera.com/node-id=${nodeId},solo.hedera.com/type=network-node-svc`];
    }
    /**
     * Build label selectors for deployment refresh by component type.
     */
    static renderComponentLabelSelectors(componentType, id) {
        switch (componentType) {
            case 'ConsensusNode': {
                return Templates.renderHaProxyLabels(id);
            }
            case 'HaProxy': {
                return Templates.renderHaProxyLabels(id);
            }
            case 'BlockNode': {
                return Templates.renderBlockNodeLabels(id);
            }
            case 'MirrorNode': {
                return Templates.renderMirrorIngressControllerLabels();
            }
            case 'RelayNode': {
                return Templates.renderRelayLabels(id);
            }
            case 'Explorer': {
                return Templates.renderExplorerLabels(id);
            }
            default: {
                return [];
            }
        }
    }
    static parseExternalBlockAddress(raw) {
        const [address, port] = raw.includes(':') ? raw.split(':') : [raw, constants.BLOCK_NODE_PORT];
        return [address, +port];
    }
    static parseBlockNodePriorityMapping(rawString, nodes) {
        const mapping = {};
        const isDefault = !rawString || rawString.split(',').length === 0;
        const nodeAliasesToPriorityMapping = isDefault
            ? nodes.map((node) => node.name)
            : rawString.split(',');
        for (const data of nodeAliasesToPriorityMapping) {
            // eslint-disable-next-line prefer-const
            let [nodeAlias, priority] = data.split('=');
            mapping[nodeAlias] = +priority || 1;
        }
        return mapping;
    }
    /**
     * @param rawString - the raw string from the unparsed [flags.blockNodeMapping, flags.externalBlockNodeMapping]
     * @param fallbackBlockNodeIds - either block node IDs or external block node IDs
     */
    static parseConsensusNodePriorityMapping(rawString, fallbackBlockNodeIds) {
        // if no nodes are specified use one and set highest priority to first block node
        const useDefault = typeof rawString !== 'string' || rawString.length === 0;
        if (useDefault) {
            const mapping = [];
            for (const [index, blockNodeId] of fallbackBlockNodeIds.entries()) {
                // set higher priority to first node
                mapping.push([blockNodeId, index === 0 ? 2 : 1]);
            }
            return mapping;
        }
        // Figure out if any priority is explicitly specified
        const hasPriority = rawString.includes('=');
        const mapping = [];
        for (const [index, data] of rawString.split(',').entries()) {
            // use specified priority if set
            if (data.includes('=')) {
                const [blockNodeId, priority] = data.split('=');
                mapping.push([+blockNodeId, +priority]);
            }
            // if any node has priority specified, default all unset to 1
            else if (hasPriority) {
                mapping.push([+data, 1]);
            }
            // if no explicit priority is specified, set higher priority to first node
            else {
                mapping.push([+data, index === 0 ? 2 : 1]);
            }
        }
        return mapping;
    }
}
//# sourceMappingURL=templates.js.map