"use strict";
// 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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MirrorNodeAdapter = void 0;
const axios_1 = __importDefault(require("axios"));
const tsyringe_1 = require("tsyringe");
const sdk_1 = require("@hiero-ledger/sdk");
const InvalidResponse_1 = require("../../../core/error/InvalidResponse");
const TransactionResponseError_1 = require("../error/TransactionResponseError");
const Constants_1 = require("../../../core/Constants");
const LogService_1 = __importDefault(require("../../../app/service/log/LogService"));
const BigDecimal_1 = __importDefault(require("../../../domain/context/shared/BigDecimal"));
const PublicKey_1 = __importDefault(require("../../../domain/context/account/PublicKey"));
const HederaId_1 = require("../../../domain/context/shared/HederaId");
const KeyProps_1 = require("../../../domain/context/account/KeyProps");
const EvmAddress_1 = __importDefault(require("../../../domain/context/contract/EvmAddress"));
const Time_1 = require("../../../core/Time");
const TransactionNotFound_1 = require("./error/TransactionNotFound");
const TransactionResultNotFound_1 = require("./error/TransactionResultNotFound");
const BalanceNotFound_1 = require("./error/BalanceNotFound");
const ErrorRetrievingEvmAddress_1 = require("./error/ErrorRetrievingEvmAddress");
let MirrorNodeAdapter = class MirrorNodeAdapter {
    instance;
    config;
    mirrorNodeConfig;
    set(mnConfig) {
        this.mirrorNodeConfig = mnConfig;
        this.instance = axios_1.default.create({
            validateStatus: function (status) {
                return (status >= 200 && status < 300) || status == 404;
            },
        });
        this.mirrorNodeConfig.baseUrl = mnConfig.baseUrl.endsWith("/") ? mnConfig.baseUrl : `${mnConfig.baseUrl}/`;
        if (this.mirrorNodeConfig.headerName && this.mirrorNodeConfig.apiKey)
            this.instance.defaults.headers.common[this.mirrorNodeConfig.headerName] = this.mirrorNodeConfig.apiKey;
    }
    async getHederaIdfromContractAddress(contractAddress) {
        if (!contractAddress)
            return "";
        if (contractAddress.length >= 40)
            return (await this.getContractInfo(contractAddress)).id;
        return contractAddress;
    }
    async getAccountInfo(accountId) {
        try {
            LogService_1.default.logTrace("Getting account info -> ", this.mirrorNodeConfig.baseUrl + "accounts/" + accountId);
            const res = await this.instance.get(this.mirrorNodeConfig.baseUrl + "accounts/" + accountId.toString());
            const account = {
                id: HederaId_1.HederaId.from(res.data.account),
                evmAddress: res.data.evm_address,
                alias: res.data.alias,
            };
            if (res.data.key)
                account.publicKey = new PublicKey_1.default({
                    key: res.data.key ? this.trimLeadingZeros(res.data.key.key) : undefined,
                    type: res.data.key ? this.getPublicKeyType(res.data.key._type) : undefined,
                });
            return account;
        }
        catch (error) {
            LogService_1.default.logError(error);
            return Promise.reject(new InvalidResponse_1.InvalidResponse(error));
        }
    }
    async getContractResults(transactionId, numberOfResultItems, evmAddress = false, timeout = 15, requestInterval = 2) {
        if (transactionId.match(TransactionResponseError_1.REGEX_TRANSACTION)) {
            transactionId = transactionId.replace("@", "-").replace(/.([^.]*)$/, "-$1");
        }
        const url = `${this.mirrorNodeConfig.baseUrl}contracts/results/${transactionId}`;
        let call_OK = false;
        const results = [];
        do {
            await Time_1.Time.delay(requestInterval, "seconds");
            timeout = timeout - requestInterval;
            this.instance
                .get(url)
                .then((response) => {
                if (response &&
                    response.status === 200 &&
                    response.data.call_result &&
                    response.data.call_result.length > 2) {
                    try {
                        call_OK = true;
                        const data = response.data.call_result;
                        if (evmAddress) {
                            results.push(response.data.address);
                            return results;
                        }
                        if (numberOfResultItems == 0) {
                            numberOfResultItems = (data.length - 2) / Constants_1.BYTES_32_LENGTH;
                        }
                        if (data && data.startsWith("0x") && data.length >= 2 + numberOfResultItems * Constants_1.BYTES_32_LENGTH) {
                            for (let i = 0; i < numberOfResultItems; i++) {
                                const start = 2 + i * Constants_1.BYTES_32_LENGTH;
                                const end = start + Constants_1.BYTES_32_LENGTH;
                                const result = `0x${data.slice(start, end)}`;
                                results.push(result);
                            }
                            return results;
                        }
                        return null;
                    }
                    catch (error) {
                        LogService_1.default.logError(error);
                        return Promise.reject(new InvalidResponse_1.InvalidResponse(error));
                    }
                }
            })
                .catch((error) => {
                LogService_1.default.logError(`Error getting contracts result for transaction ${transactionId}: ${error}`);
            });
        } while (timeout > 0 && !call_OK);
        return results;
    }
    trimLeadingZeros(publicKey) {
        return publicKey.replace(/^0+/, "");
    }
    getPublicKeyType(publicKey) {
        switch (publicKey) {
            case "ECDSA_SECP256K1":
                return KeyProps_1.KeyType.ECDSA;
            default:
                return publicKey;
        }
    }
    async getContractInfo(contractId) {
        try {
            const url = `${this.mirrorNodeConfig.baseUrl}contracts/${contractId}`;
            LogService_1.default.logTrace("Getting contract info -> ", url);
            const retry = 10;
            let i = 0;
            let response;
            do {
                if (i > 0)
                    await new Promise((resolve) => setTimeout(resolve, 2000));
                response = await this.instance.get(url);
                i++;
            } while (response.status !== 200 && i < retry);
            const contract = {
                id: response.data.contract_id,
                evmAddress: response.data.evm_address,
            };
            return contract;
        }
        catch (error) {
            LogService_1.default.logError(error);
            return Promise.reject(new InvalidResponse_1.InvalidResponse(error));
        }
    }
    async getTransactionResult(transactionId) {
        try {
            const url = this.mirrorNodeConfig.baseUrl + "contracts/results/" + transactionId;
            LogService_1.default.logTrace(url);
            const res = await this.instance.get(url);
            if (!res.data.call_result)
                throw new TransactionResultNotFound_1.TransactionResultNotFound();
            const result = {
                result: res.data.call_result.toString(),
            };
            return result;
        }
        catch (error) {
            LogService_1.default.logError(error);
            return Promise.reject(new InvalidResponse_1.InvalidResponse(error));
        }
    }
    async getTransactionFinalError(transactionId) {
        try {
            if (transactionId.match(TransactionResponseError_1.REGEX_TRANSACTION))
                transactionId = transactionId.replace("@", "-").replace(/.([^.]*)$/, "-$1");
            const url = this.mirrorNodeConfig.baseUrl + "transactions/" + transactionId;
            LogService_1.default.logTrace(url);
            await new Promise((resolve) => setTimeout(resolve, 5000));
            const res = await this.instance.get(url);
            let lastChildTransaction;
            if (res.data.transactions) {
                lastChildTransaction = res.data.transactions[res.data.transactions.length - 1];
                LogService_1.default.logError(JSON.stringify(lastChildTransaction));
            }
            else {
                throw new TransactionNotFound_1.TransactionNotFound();
            }
            const result = {
                result: lastChildTransaction.result,
            };
            return result;
        }
        catch (error) {
            LogService_1.default.logError(error);
            return Promise.reject(new InvalidResponse_1.InvalidResponse(error));
        }
    }
    async getFileIdFromTransaction(transactionId, timeout = 20, requestInterval = 2) {
        if (transactionId.match(TransactionResponseError_1.REGEX_TRANSACTION)) {
            transactionId = transactionId.replace("@", "-").replace(/.([^.]*)$/, "-$1");
        }
        const url = this.mirrorNodeConfig.baseUrl + "transactions/" + transactionId;
        LogService_1.default.logTrace(`[MirrorNode] Querying FileCreate transaction: ${url}`);
        let remainingTimeout = timeout;
        while (remainingTimeout > 0) {
            await Time_1.Time.delay(requestInterval, "seconds");
            remainingTimeout -= requestInterval;
            try {
                const res = await this.instance.get(url);
                const tx = res.data?.transactions?.[0];
                if (tx?.entity_id) {
                    LogService_1.default.logTrace(`[MirrorNode] Found entity_id: ${tx.entity_id}`);
                    return tx.entity_id;
                }
                LogService_1.default.logTrace(`[MirrorNode] entity_id not ready yet (${remainingTimeout}s remaining), tx: ${JSON.stringify(tx)}`);
            }
            catch (error) {
                LogService_1.default.logTrace(`[MirrorNode] Error querying transaction (${remainingTimeout}s remaining): ${error}`);
            }
        }
        return Promise.reject(new InvalidResponse_1.InvalidResponse("No entity_id in FileCreateTransaction receipt after timeout"));
    }
    async accountToEvmAddress(accountId) {
        try {
            const accountInfo = await this.getAccountInfo(accountId);
            if (accountInfo.evmAddress) {
                return new EvmAddress_1.default(accountInfo.evmAddress);
            }
            else if (accountInfo.publicKey) {
                return this.getAccountEvmAddressFromPrivateKeyType(accountInfo.publicKey.type, accountInfo.publicKey.key, accountId);
            }
            else {
                return Promise.reject("");
            }
        }
        catch (e) {
            throw new ErrorRetrievingEvmAddress_1.ErrorRetrievingEvmAddress(accountId, e);
        }
    }
    async getAccountEvmAddressFromPrivateKeyType(privateKeyType, publicKey, accountId) {
        switch (privateKeyType) {
            case KeyProps_1.KeyType.ECDSA:
                return new EvmAddress_1.default(sdk_1.PublicKey.fromString(publicKey).toEthereumAddress());
            default:
                return new EvmAddress_1.default("0x" + (await this.getContractInfo(accountId)).evmAddress);
        }
    }
    async getHBARBalance(accountId) {
        try {
            const url = `${this.mirrorNodeConfig.baseUrl}balances?account.id=${accountId.toString()}`;
            LogService_1.default.logTrace(url);
            const res = await this.instance.get(url);
            if (!res.data.balances)
                throw new BalanceNotFound_1.BalanceNotFound();
            return BigDecimal_1.default.fromString(res.data.balances[res.data.balances.length - 1].balance.toString());
        }
        catch (error) {
            LogService_1.default.logError(error);
            return Promise.reject(new InvalidResponse_1.InvalidResponse(error));
        }
    }
};
exports.MirrorNodeAdapter = MirrorNodeAdapter;
exports.MirrorNodeAdapter = MirrorNodeAdapter = __decorate([
    (0, tsyringe_1.singleton)()
], MirrorNodeAdapter);
