"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GenerateArbitrageSignal = void 0;
const SupportedNetwork_js_1 = require("../../domain/value-objects/SupportedNetwork.js");
const AppError_js_1 = require("../../shared/errors/AppError.js");
const random_js_1 = require("../../shared/utils/random.js");
const time_js_1 = require("../../shared/utils/time.js");
class GenerateArbitrageSignal {
    marketDataRepository;
    platformRepository;
    getAdminJwt;
    cleanupWorker;
    logger;
    constructor(marketDataRepository, platformRepository, getAdminJwt, cleanupWorker, logger) {
        this.marketDataRepository = marketDataRepository;
        this.platformRepository = platformRepository;
        this.getAdminJwt = getAdminJwt;
        this.cleanupWorker = cleanupWorker;
        this.logger = logger;
    }
    async execute() {
        const [trending, networks, tokens] = await Promise.all([
            this.marketDataRepository.getTopTrendingTokens(10).catch(() => []),
            this.platformRepository.listNetworks(),
            this.platformRepository.listTokens()
        ]);
        const candidate = this.findExistingToken(trending, tokens);
        const fallback = candidate ? undefined : this.findPlatformFallbackToken(tokens);
        let token = candidate?.token ?? fallback?.token;
        let marketToken = candidate?.marketToken ?? fallback?.marketToken;
        let marketNetwork = candidate?.network ?? fallback?.network;
        if (!token || !marketToken) {
            const importCandidate = this.findImportCandidate(trending);
            const network = networks.find((item) => this.sameNetwork(item.name, importCandidate.network));
            if (!network) {
                throw new AppError_js_1.AppError("No supported platform network matched current CoinMarketCap trending contracts.", "NO_NETWORK_MATCH", true);
            }
            const jwt = await this.getAdminJwt.execute();
            this.logger?.info("Importing arbitrage token.", { symbol: importCandidate.marketToken.symbol, networkId: network.id });
            const imported = await this.importTokenOrFallback(network.id, importCandidate.marketToken, jwt);
            if (!imported) {
                throw new AppError_js_1.AppError("The token could not be imported or synced onto the platform.", "TOKEN_IMPORT_FAILED", true);
            }
            token = imported.token;
            marketToken = imported.marketToken;
            marketNetwork = imported.network;
            this.logger?.info("Arbitrage token imported.", { tokenId: token.id, symbol: token.symbol });
        }
        const actualPrice = marketToken.priceUsd ?? (await this.platformRepository.getTokenPrice(token.id, token.networkId));
        if (!actualPrice) {
            throw new AppError_js_1.ExternalApiError("Unable to determine current token price.", "PRICE_UNAVAILABLE");
        }
        const side = (0, random_js_1.chooseOne)(["BUY", "SELL"]);
        const spread = side === "BUY" ? (0, random_js_1.randomFloat)(-25, -15, 2) : (0, random_js_1.randomFloat)(15, 25, 2);
        const platformPrice = Number((actualPrice * (1 + spread / 100)).toFixed(8));
        const windowMinutes = (0, random_js_1.randomInt)(1, 6);
        const executionWindow = `${windowMinutes} minutes`;
        const jwt = await this.getAdminJwt.execute();
        try {
            this.logger?.info("Setting trailing custom price for arbitrage.", { tokenId: token.id, symbol: token.symbol, spread });
            await this.platformRepository.setCustomPrice(token.id, platformPrice, "trailing", jwt, spread, `Arbitrage spread ${spread.toFixed(2)}%`);
            this.logger?.info("Trailing custom price set.", { tokenId: token.id, symbol: token.symbol });
        }
        catch (error) {
            this.logger?.error("Failed to set trailing custom price for arbitrage.", {
                tokenId: token.id,
                symbol: token.symbol,
                error: error instanceof Error ? error.message : String(error)
            });
            if (!this.isUnauthorized(error))
                throw error;
            try {
                const refreshedJwt = await this.getAdminJwt.execute(true);
                await this.platformRepository.setCustomPrice(token.id, platformPrice, "trailing", refreshedJwt, spread, `Arbitrage spread ${spread.toFixed(2)}%`);
                this.logger?.info("Trailing custom price set for arbitrage after JWT refresh.", { tokenId: token.id, symbol: token.symbol });
            }
            catch (retryError) {
                this.logger?.error("Failed to set trailing custom price for arbitrage after JWT refresh.", {
                    tokenId: token.id,
                    symbol: token.symbol,
                    error: retryError instanceof Error ? retryError.message : String(retryError)
                });
            }
        }
        await this.cleanupWorker.schedule({
            tokenId: token.id,
            networkId: token.networkId ?? "",
            tokenSymbol: token.symbol || marketToken.symbol,
            priceGap: spread,
            reason: `Arbitrage signal window expired (${executionWindow})`,
            expiresAt: (0, time_js_1.minutesFromNow)(windowMinutes)
        });
        return {
            tokenName: token.name || marketToken.name,
            symbol: token.symbol || marketToken.symbol,
            network: token.networkName ?? marketNetwork,
            contractAddress: token.contractAddress ?? this.contractForNetwork(marketToken, marketNetwork),
            category: "Arbitrage Opportunities",
            side,
            strategyName: "Arbitrage Hawk",
            actualMarketPrice: actualPrice,
            platformPrice,
            entryPrice: actualPrice,
            targetPrice: platformPrice,
            spreadPercentage: spread,
            estimatedProfitPercentage: Math.abs(spread),
            riskLevel: "High",
            suggestedAmountUsd: (0, random_js_1.randomInt)(969, 9696),
            executionWindow
        };
    }
    findExistingToken(trending, tokens) {
        for (const marketToken of trending) {
            for (const contract of marketToken.contractAddresses.filter((item) => (0, SupportedNetwork_js_1.isSupportedArbitrageNetwork)(item.network))) {
                const token = tokens.find((platformToken) => {
                    const contractMatches = platformToken.contractAddress &&
                        platformToken.contractAddress.toLowerCase() === contract.address.toLowerCase();
                    const symbolNetworkMatches = platformToken.symbol.toUpperCase() === marketToken.symbol.toUpperCase() &&
                        this.sameNetwork(platformToken.networkName, contract.network);
                    return contractMatches || symbolNetworkMatches;
                });
                if (token)
                    return { marketToken, token, network: contract.network };
            }
        }
        return undefined;
    }
    findPlatformFallbackToken(tokens) {
        const token = tokens.find((item) => item.isTopToken && item.priceUsd && item.priceUsd > 0) ??
            tokens.find((item) => item.priceUsd && item.priceUsd > 0) ??
            tokens.find((item) => item.isTopToken) ??
            tokens[0];
        if (!token)
            return undefined;
        const network = token.networkName ?? token.networkId;
        return {
            token,
            network,
            marketToken: {
                cmcId: 0,
                name: token.name,
                symbol: token.symbol,
                priceUsd: token.priceUsd,
                contractAddresses: token.contractAddress && network
                    ? [
                        {
                            network,
                            address: token.contractAddress
                        }
                    ]
                    : []
            }
        };
    }
    findImportCandidate(trending) {
        for (const marketToken of trending) {
            const contract = marketToken.contractAddresses.find((item) => (0, SupportedNetwork_js_1.isSupportedArbitrageNetwork)(item.network));
            if (contract)
                return { marketToken, network: contract.network };
        }
        throw new AppError_js_1.AppError("CoinMarketCap trending tokens did not include supported EVM contracts.", "NO_SUPPORTED_TRENDING", true);
    }
    async importTokenOrFallback(networkId, marketToken, jwt) {
        const contract = marketToken.contractAddresses.find((item) => (0, SupportedNetwork_js_1.isSupportedArbitrageNetwork)(item.network));
        if (!contract)
            return undefined;
        try {
            await this.platformRepository.importToken(networkId, contract.address, jwt, marketToken.logoUrl);
        }
        catch (importError) {
            // Fallback to broad token sync if single-token import fails.
            await this.platformRepository.syncTopTokens(networkId, jwt);
        }
        const refreshedTokens = await this.platformRepository.listTokens();
        const refreshed = this.findExistingToken([marketToken], refreshedTokens);
        return refreshed;
    }
    sameNetwork(left, right) {
        if (!left || !right)
            return false;
        const a = left.toLowerCase();
        const b = right.toLowerCase();
        return a.includes(b) || b.includes(a) || (a.includes("bnb") && b.includes("binance")) || (a.includes("polygon") && b.includes("polygon"));
    }
    contractForNetwork(marketToken, network) {
        if (!network)
            return undefined;
        return marketToken.contractAddresses.find((item) => this.sameNetwork(item.network, network))?.address;
    }
    isUnauthorized(error) {
        if (!(error instanceof AppError_js_1.ExternalApiError))
            return false;
        const cause = error.cause;
        return cause?.status === 401 || cause?.status === 403;
    }
}
exports.GenerateArbitrageSignal = GenerateArbitrageSignal;
