package com.fanitas.evelyn.service.burner.impl.ref;

import com.fanitas.evelyn.service.burner.EvelynIOUBurnerService;
import com.r35157.assetaz.services.cis.CurrencyIdentityService;
import com.r35157.assetaz.services.cis.ExternalCurrencyReference;
import com.r35157.assetaz.services.ticker.PriceObservation;
import com.r35157.assetaz.services.ticker.TickerService;
import com.r35157.assetaz.valuetypes.CurrencyType;
import com.r35157.cryptowallet.solana.SolanaWallet;
import com.r35157.libs.jupiter.JupiterTransactionOutcomeException;
import com.r35157.libs.jupiter.swap.JupiterSwapService;
import com.r35157.libs.solana.SPLTokenSupply;
import com.r35157.libs.solana.SolanaAccountInfo;
import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.SolanaCommitment;
import com.r35157.libs.solana.SolanaTransactionOutcome;
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
import com.r35157.libs.valuetypes.basic.AssetPrice;
import com.r35157.libs.valuetypes.basic.MoneyAmount;
import com.r35157.libs.valuetypes.basic.TradingPair;
import com.r35157.nenjim.component.NenjimApplication;
import com.r35157.service.notification.BoundNotificationService;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static com.r35157.assetaz.services.cis.CurrencyTypeIds.EVE_ID;
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.SOLANA_ID;
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.USDC_ID;
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.USDT_ID;
import static com.r35157.assetaz.services.cis.ExternalCurrencyReference.SOLANA_MINT_NAMESPACE;

/**
 * Reference implementation of the Evelyn IOU buyback-and-burn lifecycle.
 */
public final class EvelynIOUBurnerServiceImpl
        implements EvelynIOUBurnerService, NenjimApplication {
    /**
     * Creates an Evelyn IOU burner with all runtime dependencies and current
     * operating policy.
     *
     * <p>Each entry in {@code inputCurrencyMinimumReserves} identifies one
     * accepted input through {@code currencyType().id()} and gives the minimum
     * amount of that same currency that must remain through {@code amount()}.
     * Entry order is buyback priority. The list is copied defensively, and an
     * empty list permits direct EVE deposits to be burned without buybacks.
     * Supplied names and symbols are ignored in favor of CIS-owned canonical
     * metadata. Solana mint addresses and token programs are resolved
     * internally and are not constructor configuration.</p>
     *
     * @param burnerWallet dedicated wallet whose balances are inspected and
     *                     whose complete EVE balance is burned
     * @param swapService exact-input Jupiter service; normal return already
     *                    means the swap reached {@code CONFIRMED}
     * @param solanaBlockChain Solana access used for mint ownership, token
     *                         precision, and burn finalization
     * @param tickerService latest-price source for input/USDC and EVE/USDT
     * @param currencyIdentityService authoritative resolver for AssetAZ UUIDs
     *                                and Solana mint references
     * @param notificationService fully bound synchronous destination for the
     *                            single post-finalization notification attempt
     * @param inputCurrencyMinimumReserves ordered accepted-input list; entries
     *        must be non-null, use distinct known non-EVE UUIDs, and contain a
     *        non-null reserve amount greater than or equal to zero; every
     *        non-SOL UUID must have exactly one non-blank CIS
     *        {@code solana-mint} external reference
     * @param maximumSwapAmountInUSDC strictly positive USDC-equivalent ceiling
     *                                for one buyback; it does not limit burns
     * @param maximumSlippageBps Jupiter slippage limit in basis points from 0
     *                           through 10000 inclusive
     * @param maximumPriceAge strictly positive maximum observation age in
     *                        milliseconds for both input and EVE prices
     * @param burnFinalizationTimeout strictly positive timeout passed unchanged
     *                                to each {@code FINALIZED} burn await
     * @param iterationInterval strictly positive fixed delay measured from one
     *                          completed iteration to the next
     * @throws NullPointerException if an injected service, configuration
     *                              object, list entry, reserve amount,
     *                              configured currency, or UUID is null
     * @throws IllegalArgumentException if a reserve or scalar limit is out of
     *                                  range, a duration is non-positive, a
     *                                  UUID is unknown or duplicated, EVE is
     *                                  configured as input, or a non-SOL input
     *                                  lacks exactly one usable Solana mint
     */
    public EvelynIOUBurnerServiceImpl(
            @NotNull SolanaWallet burnerWallet,
            @NotNull JupiterSwapService swapService,
            @NotNull SolanaBlockChain solanaBlockChain,
            @NotNull TickerService tickerService,
            @NotNull CurrencyIdentityService currencyIdentityService,
            @NotNull BoundNotificationService notificationService,
            @NotNull List<MoneyAmount> inputCurrencyMinimumReserves,
            @NotNull ΩUSDCAmountΩ maximumSwapAmountInUSDC,
            int maximumSlippageBps,
            ΩmilliSecondsΩ maximumPriceAge,
            @NotNull Duration burnFinalizationTimeout,
            @NotNull Duration iterationInterval
    ) {
        this.burnerWallet = Objects.requireNonNull(
                burnerWallet,
                "burnerWallet"
        );
        this.swapService = Objects.requireNonNull(
                swapService,
                "swapService"
        );
        this.solanaBlockChain = Objects.requireNonNull(
                solanaBlockChain,
                "solanaBlockChain"
        );
        this.tickerService = Objects.requireNonNull(
                tickerService,
                "tickerService"
        );
        this.currencyIdentityService = Objects.requireNonNull(
                currencyIdentityService,
                "currencyIdentityService"
        );
        this.notificationService = Objects.requireNonNull(
                notificationService,
                "notificationService"
        );

        Objects.requireNonNull(
                maximumSwapAmountInUSDC,
                "maximumSwapAmountInUSDC"
        );
        if (maximumSwapAmountInUSDC.signum() <= 0) {
            throw new IllegalArgumentException(
                    "maximumSwapAmountInUSDC must be greater than zero"
            );
        }
        if (maximumSlippageBps < 0
                || maximumSlippageBps > MAXIMUM_SLIPPAGE_BPS) {
            throw new IllegalArgumentException(
                    "maximumSlippageBps must be between 0 and "
                            + MAXIMUM_SLIPPAGE_BPS
            );
        }
        if (maximumPriceAge <= 0) {
            throw new IllegalArgumentException(
                    "maximumPriceAge must be greater than zero milliseconds"
            );
        }

        this.burnFinalizationTimeout = requirePositiveDuration(
                burnFinalizationTimeout,
                "burnFinalizationTimeout"
        );
        Duration validatedIterationInterval = requirePositiveDuration(
                iterationInterval,
                "iterationInterval"
        );
        this.iterationIntervalNanos = TimeUnit.NANOSECONDS.convert(
                validatedIterationInterval
        );
        if (iterationIntervalNanos <= 0) {
            throw new IllegalArgumentException(
                    "iterationInterval must be representable as at least one "
                            + "nanosecond"
            );
        }

        this.maximumSwapAmountInUSDC = maximumSwapAmountInUSDC;
        this.maximumSlippageBps = maximumSlippageBps;
        this.maximumPriceAge = maximumPriceAge;
        this.maximumPriceAgeDuration = Duration.ofMillis(maximumPriceAge);

        CurrencyType canonicalEve = requireCanonicalCurrency(
                EVE_ID,
                "Evelyn IOU"
        );
        this.canonicalUsdc = requireCanonicalCurrency(USDC_ID, "USDC");
        CurrencyType canonicalUsdt = requireCanonicalCurrency(
                USDT_ID,
                "USDT"
        );
        this.eveUsdtTradingPair = new TradingPair(
                canonicalEve,
                canonicalUsdt
        );
        this.inputCurrencyMinimumReserves = validateAndCopyInputs(
                inputCurrencyMinimumReserves
        );
    }

    @Override
    public synchronized void start() {
        if (worker != null || stopping) {
            throw new IllegalStateException(
                    "Evelyn IOU burner is already running or stopping"
            );
        }

        resetProcessLocalState();
        ScheduledExecutorService newWorker;
        try {
            newWorker = Executors.newSingleThreadScheduledExecutor(
                    runnable -> {
                        Thread thread = new Thread(
                                runnable,
                                WORKER_THREAD_NAME
                        );
                        thread.setDaemon(true);
                        return thread;
                    }
            );
        } catch (RuntimeException exception) {
            throw new IllegalStateException(
                    "Could not create Evelyn IOU burner worker",
                    exception
            );
        }

        worker = newWorker;
        try {
            newWorker.scheduleWithFixedDelay(
                    this::runIterationSafely,
                    0,
                    iterationIntervalNanos,
                    TimeUnit.NANOSECONDS
            );
        } catch (RuntimeException exception) {
            newWorker.shutdownNow();
            worker = null;
            resetProcessLocalState();
            throw new IllegalStateException(
                    "Could not start Evelyn IOU burner worker",
                    exception
            );
        }
    }

    @Override
    public void stop() {
        ScheduledExecutorService workerToStop;
        synchronized (this) {
            if (worker == null) {
                resetProcessLocalState();
                return;
            }
            if (stopping) {
                throw new IllegalStateException(
                        "Evelyn IOU burner is already stopping"
                );
            }
            stopping = true;
            workerToStop = worker;
        }

        workerToStop.shutdownNow();
        boolean terminated;
        try {
            terminated = workerToStop.awaitTermination(
                    WORKER_TERMINATION_TIMEOUT_SECONDS,
                    TimeUnit.SECONDS
            );
        } catch (InterruptedException exception) {
            workerToStop.shutdownNow();
            boolean terminatedAfterInterruption = workerToStop.isTerminated();
            synchronized (this) {
                if (terminatedAfterInterruption && worker == workerToStop) {
                    worker = null;
                    resetProcessLocalState();
                }
                stopping = false;
            }
            Thread.currentThread().interrupt();
            throw new IllegalStateException(
                    "Interrupted while stopping Evelyn IOU burner",
                    exception
            );
        }

        synchronized (this) {
            if (!terminated) {
                stopping = false;
                throw new IllegalStateException(
                        "Evelyn IOU burner worker did not terminate cleanly"
                );
            }
            if (worker == workerToStop) {
                worker = null;
            }
            resetProcessLocalState();
            stopping = false;
        }
    }

    private void runIterationSafely() {
        try {
            runIteration();
        } catch (InterruptedException exception) {
            Thread.currentThread().interrupt();
            if (!isStopping()) {
                log.warn("Evelyn IOU burner iteration was interrupted");
            }
        } catch (RuntimeException exception) {
            log.error(
                    "Unexpected Evelyn IOU burner iteration failure",
                    exception
            );
        }
    }

    private void runIteration() throws InterruptedException {
        if (pendingBurn != null) {
            awaitPendingBurn();
            return;
        }

        if (!swapsSuspended) {
            attemptAtMostOneSwap();
        }
        processCompleteEveBalance();
    }

    private void attemptAtMostOneSwap() throws InterruptedException {
        for (MoneyAmount minimumReserve : inputCurrencyMinimumReserves) {
            UUID currencyId = minimumReserve.currencyType().id();
            CurrencyType canonicalCurrency;
            try {
                canonicalCurrency = requireCanonicalCurrency(
                        currencyId,
                        "configured input"
                );
            } catch (RuntimeException exception) {
                warnSkippedInput(
                        currencyId,
                        "canonical currency resolution",
                        exception
                );
                continue;
            }

            boolean nativeSolana = SOLANA_ID.equals(currencyId);
            ΩSPLMintAddressΩ inputMint;
            ΩAmountΩ balance;
            ΩamountDecimalsΩ decimals;
            SolanaSPLTokenProgram inputTokenProgram = null;
            try {
                inputMint = requireUniqueSolanaMint(currencyId);
                if (nativeSolana) {
                    if (!CANONICAL_SOLANA_JUPITER_MINT.equals(inputMint)) {
                        throw new IllegalStateException(
                                "CIS returned a non-canonical Jupiter mint for "
                                        + "native SOL"
                        );
                    }
                    MoneyAmount solanaBalance = burnerWallet.getSolanaBalance();
                    if (solanaBalance == null
                            || solanaBalance.amount() == null) {
                        throw new IllegalStateException(
                                "Wallet returned no native SOL balance amount"
                        );
                    }
                    balance = solanaBalance.amount();
                    decimals = NATIVE_SOLANA_DECIMALS;
                } else {
                    inputTokenProgram = resolveSupportedTokenProgram(
                            inputMint
                    );
                    balance = burnerWallet.getSPLTokenBalance(
                            inputMint,
                            inputTokenProgram
                    );
                    if (balance == null) {
                        continue;
                    }
                    decimals = -1;
                }
            } catch (IOException | RuntimeException exception) {
                warnSkippedInput(
                        currencyId,
                        "balance or Solana representation",
                        exception
                );
                continue;
            }

            if (balance.signum() < 0) {
                warnSkippedInput(
                        currencyId,
                        "negative wallet balance",
                        null
                );
                continue;
            }
            ΩAmountΩ availableForSwap = balance.subtract(
                    minimumReserve.amount()
            );
            if (availableForSwap.signum() <= 0) {
                continue;
            }

            ΩPriceΩ inputPriceInUsdc;
            if (USDC_ID.equals(currencyId) || USDT_ID.equals(currencyId)) {
                inputPriceInUsdc = BigDecimal.ONE;
            } else {
                inputPriceInUsdc = findFreshPrice(
                        new TradingPair(canonicalCurrency, canonicalUsdc)
                );
                if (inputPriceInUsdc == null) {
                    continue;
                }
            }

            if (!nativeSolana) {
                try {
                    decimals = resolveMintDecimals(
                            inputMint,
                            inputTokenProgram
                    );
                } catch (IOException | RuntimeException exception) {
                    warnSkippedInput(
                            currencyId,
                            "mint precision",
                            exception
                    );
                    continue;
                }
            }

            ΩAmountΩ maximumInputAmount = maximumSwapAmountInUSDC.divide(
                    inputPriceInUsdc,
                    decimals,
                    RoundingMode.DOWN
            );
            ΩAmountΩ amountToSwap = availableForSwap
                    .min(maximumInputAmount)
                    .setScale(decimals, RoundingMode.DOWN);
            if (amountToSwap.signum() <= 0) {
                continue;
            }

            attemptSwap(
                    canonicalCurrency,
                    inputMint,
                    amountToSwap
            );
            return;
        }
    }

    private void attemptSwap(
            CurrencyType canonicalCurrency,
            ΩSPLMintAddressΩ inputMint,
            ΩAmountΩ amountToSwap
    ) throws InterruptedException {
        try {
            swapService.swap(
                    inputMint,
                    amountToSwap,
                    EVELYN_IOU_MINT,
                    maximumSlippageBps
            );
        } catch (JupiterTransactionOutcomeException exception) {
            SolanaTransactionOutcome outcome = exception.outcome();
            switch (outcome.status()) {
                case FAILED -> log.error(
                        "Evelyn IOU buyback swap failed on-chain: "
                                + "currencyId={}, signature={}, slot={}, "
                                + "failureDetails={}",
                        canonicalCurrency.id(),
                        exception.transactionSignature(),
                        outcome.slot(),
                        outcome.failureDetails()
                );
                case TIMED_OUT -> {
                    swapsSuspended = true;
                    log.error(
                            "Evelyn IOU buyback swap outcome is unknown after "
                                    + "timeout; later swaps are suspended: "
                                    + "currencyId={}, signature={}",
                            canonicalCurrency.id(),
                            exception.transactionSignature()
                    );
                }
                case SUCCEEDED -> throw new IllegalStateException(
                        "A successful Jupiter outcome was reported as an "
                                + "exception"
                );
            }
        } catch (IOException exception) {
            swapsSuspended = true;
            log.error(
                    "Evelyn IOU buyback swap may have an unknown outcome; "
                            + "later swaps are suspended: currencyId={}, "
                            + "failureType={}",
                    canonicalCurrency.id(),
                    exception.getClass().getName()
            );
        } catch (InterruptedException exception) {
            swapsSuspended = true;
            throw exception;
        } catch (RuntimeException exception) {
            log.error(
                    "Evelyn IOU buyback swap failed before a confirmed "
                            + "result: currencyId={}, failureType={}",
                    canonicalCurrency.id(),
                    exception.getClass().getName()
            );
        }
    }

    private void processCompleteEveBalance() throws InterruptedException {
        ΩAmountΩ eveBalance;
        try {
            SolanaSPLTokenProgram eveTokenProgram =
                    resolveSupportedTokenProgram(EVELYN_IOU_MINT);
            eveBalance = burnerWallet.getSPLTokenBalance(
                    EVELYN_IOU_MINT,
                    eveTokenProgram
            );
        } catch (IOException | RuntimeException exception) {
            log.warn(
                    "Could not reload complete Evelyn IOU balance; burn is "
                            + "postponed: failureType={}",
                    exception.getClass().getName()
            );
            return;
        }

        if (eveBalance == null || eveBalance.signum() == 0) {
            return;
        }
        if (eveBalance.signum() < 0) {
            log.warn(
                    "Wallet returned a negative Evelyn IOU balance; burn is "
                            + "postponed"
            );
            return;
        }
        if (burnsSuspended) {
            log.warn(
                    "Evelyn IOU burn submissions remain suspended because a "
                            + "prior signature-less submission outcome may be "
                            + "unknown"
            );
            return;
        }

        ΩPriceΩ evePriceInUsdt = findFreshPrice(eveUsdtTradingPair);
        if (evePriceInUsdt == null) {
            return;
        }
        ΩAmountΩ dollarValue = eveBalance.multiply(evePriceInUsdt);

        ΩSolanaTransactionSignatureΩ burnSignature;
        try {
            burnSignature = burnerWallet.burnSPLToken(
                    EVELYN_IOU_MINT,
                    eveBalance
            );
        } catch (IOException exception) {
            burnsSuspended = true;
            log.error(
                    "Evelyn IOU burn submission may have an unknown outcome; "
                            + "later burns are suspended: failureType={}",
                    exception.getClass().getName()
            );
            return;
        } catch (InterruptedException exception) {
            burnsSuspended = true;
            throw exception;
        } catch (IllegalArgumentException | IllegalStateException exception) {
            log.error(
                    "Evelyn IOU burn was rejected before a usable signature: "
                            + "failureType={}",
                    exception.getClass().getName()
            );
            return;
        } catch (RuntimeException exception) {
            burnsSuspended = true;
            log.error(
                    "Evelyn IOU burn failed without a usable signature and "
                            + "may have an unknown outcome; later burns are "
                            + "suspended: failureType={}",
                    exception.getClass().getName()
            );
            return;
        }

        if (burnSignature == null || burnSignature.isBlank()) {
            burnsSuspended = true;
            log.error(
                    "Evelyn IOU burn returned no usable signature; later "
                            + "burns are suspended because the outcome may be "
                            + "unknown"
            );
            return;
        }

        pendingBurn = new PendingBurn(
                burnSignature,
                eveBalance,
                dollarValue
        );
        awaitPendingBurn();
    }

    private void awaitPendingBurn() throws InterruptedException {
        PendingBurn burn = pendingBurn;
        if (burn == null) {
            return;
        }

        SolanaTransactionOutcome outcome;
        try {
            outcome = solanaBlockChain.awaitTransaction(
                    burn.transactionSignature(),
                    SolanaCommitment.FINALIZED,
                    burnFinalizationTimeout
            );
        } catch (IOException exception) {
            log.warn(
                    "Could not determine FINALIZED Evelyn IOU burn outcome; "
                            + "the pending signature is retained: signature={}, "
                            + "failureType={}",
                    burn.transactionSignature(),
                    exception.getClass().getName()
            );
            return;
        }

        if (outcome == null) {
            log.error(
                    "Solana returned no outcome while awaiting Evelyn IOU burn; "
                            + "the pending signature is retained: signature={}",
                    burn.transactionSignature()
            );
            return;
        }

        switch (outcome.status()) {
            case SUCCEEDED -> notifyFinalizedBurnOnce(burn);
            case FAILED -> {
                log.error(
                        "Evelyn IOU burn failed on-chain at FINALIZED: "
                                + "signature={}, slot={}, failureDetails={}",
                        burn.transactionSignature(),
                        outcome.slot(),
                        outcome.failureDetails()
                );
                pendingBurn = null;
            }
            case TIMED_OUT -> log.warn(
                    "Evelyn IOU burn did not reach a definitive FINALIZED "
                            + "outcome; the pending signature is retained: "
                            + "signature={}",
                    burn.transactionSignature()
            );
        }
    }

    private void notifyFinalizedBurnOnce(PendingBurn burn) {
        ΩNotificationMessageΩ message = formatNotification(burn);

        // Clearing before push makes this invocation the only delivery attempt
        // even if the bound transport throws an unchecked failure.
        pendingBurn = null;
        try {
            notificationService.push(message);
        } catch (IOException | RuntimeException exception) {
            log.error(
                    "Evelyn IOU burn notification delivery failed and will not "
                            + "be retried: signature={}, failureType={}",
                    burn.transactionSignature(),
                    exception.getClass().getName()
            );
        }
    }

    private ΩNotificationMessageΩ formatNotification(PendingBurn burn) {
        String eveAmount = burn.eveAmount()
                .stripTrailingZeros()
                .toPlainString();
        String dollarValue = burn.dollarValue()
                .setScale(2, RoundingMode.HALF_UP)
                .toPlainString();
        return "🔥🔥🔥 We have bought back and burned "
                + eveAmount
                + " EVE (Evelyn IOU tokens, value $"
                + dollarValue
                + ") to reduce the circulating supply! 🔥🔥🔥\n\n"
                + "Proof:\n"
                + "https://solscan.io/tx/"
                + burn.transactionSignature();
    }

    private ΩPriceΩ findFreshPrice(TradingPair tradingPair) {
        PriceObservation observation;
        try {
            observation = tickerService.getLatestPrice(tradingPair);
        } catch (RuntimeException exception) {
            log.warn(
                    "No usable price is available; operation is postponed or "
                            + "the input is skipped: tradingPair={}, "
                            + "baseCurrencyId={}, "
                            + "failureType={}",
                    tradingPair,
                    tradingPair.base().id(),
                    exception.getClass().getName()
            );
            return null;
        }

        if (observation == null
                || observation.observedAt() == null
                || observation.price() == null) {
            log.warn(
                    "Price observation is incomplete; operation is postponed "
                            + "or the input is skipped: tradingPair={}, "
                            + "baseCurrencyId={}",
                    tradingPair,
                    tradingPair.base().id()
            );
            return null;
        }

        AssetPrice observedPrice = observation.price();
        if (observedPrice.tradingPair() == null
                || !tradingPair.equals(observedPrice.tradingPair())
                || observedPrice.price() == null
                || observedPrice.price().signum() <= 0) {
            log.warn(
                    "Price observation is invalid; operation is postponed or "
                            + "the input is skipped: tradingPair={}, "
                            + "baseCurrencyId={}",
                    tradingPair,
                    tradingPair.base().id()
            );
            return null;
        }

        Duration age = Duration.between(
                observation.observedAt(),
                Instant.now()
        );
        if (age.compareTo(maximumPriceAgeDuration) > 0) {
            log.warn(
                    "Price observation is too old; operation is postponed or "
                            + "the input is skipped: tradingPair={}, "
                            + "baseCurrencyId={}, observedAt={}, "
                            + "maximumAgeMillis={}",
                    tradingPair,
                    tradingPair.base().id(),
                    observation.observedAt(),
                    maximumPriceAge
            );
            return null;
        }
        return observedPrice.price();
    }

    private SolanaSPLTokenProgram resolveSupportedTokenProgram(
            ΩSPLMintAddressΩ mintAddress
    ) throws IOException, InterruptedException {
        SolanaAccountInfo mintAccount = solanaBlockChain.getAccountInfo(
                mintAddress
        );
        if (mintAccount == null) {
            throw new IllegalStateException(
                    "Solana mint account does not exist: " + mintAddress
            );
        }

        for (SolanaSPLTokenProgram tokenProgram
                : SolanaSPLTokenProgram.values()) {
            if (tokenProgram.getAddress().equals(mintAccount.owner())) {
                return tokenProgram;
            }
        }
        throw new IllegalStateException(
                "Solana mint is owned by an unsupported token program: "
                        + mintAddress
        );
    }

    private ΩamountDecimalsΩ resolveMintDecimals(
            ΩSPLMintAddressΩ mintAddress,
            SolanaSPLTokenProgram tokenProgram
    ) throws IOException, InterruptedException {
        SPLTokenSupply supply = solanaBlockChain.getSPLTokenSupply(
                mintAddress,
                tokenProgram
        );
        if (supply == null) {
            throw new IllegalStateException(
                    "Solana returned no supply metadata for mint "
                            + mintAddress
            );
        }
        if (!mintAddress.equals(supply.mintAddress())) {
            throw new IllegalStateException(
                    "Solana returned supply metadata for an unexpected mint"
            );
        }
        if (!tokenProgram.getAddress().equals(supply.programId())) {
            throw new IllegalStateException(
                    "Solana returned an unexpected token program for mint "
                            + mintAddress
            );
        }
        if (supply.decimals() < 0
                || supply.decimals() > MAXIMUM_SPL_TOKEN_DECIMALS) {
            throw new IllegalStateException(
                    "Solana returned invalid decimal precision for mint "
                            + mintAddress
            );
        }
        return supply.decimals();
    }

    private List<MoneyAmount> validateAndCopyInputs(
            List<MoneyAmount> configuredInputs
    ) {
        Objects.requireNonNull(
                configuredInputs,
                "inputCurrencyMinimumReserves"
        );
        List<MoneyAmount> validatedInputs = new ArrayList<>(
                configuredInputs.size()
        );
        Set<UUID> configuredCurrencyIds = new HashSet<>();

        for (int index = 0; index < configuredInputs.size(); index++) {
            MoneyAmount minimumReserve = Objects.requireNonNull(
                    configuredInputs.get(index),
                    "inputCurrencyMinimumReserves[" + index + "]"
            );
            Objects.requireNonNull(
                    minimumReserve.amount(),
                    "inputCurrencyMinimumReserves[" + index + "].amount"
            );
            if (minimumReserve.amount().signum() < 0) {
                throw new IllegalArgumentException(
                        "Input currency minimum reserve must be zero or "
                                + "greater at index " + index
                );
            }

            CurrencyType configuredCurrency = Objects.requireNonNull(
                    minimumReserve.currencyType(),
                    "inputCurrencyMinimumReserves[" + index
                            + "].currencyType"
            );
            UUID currencyId = Objects.requireNonNull(
                    configuredCurrency.id(),
                    "inputCurrencyMinimumReserves[" + index
                            + "].currencyType.id"
            );
            requireCanonicalCurrency(currencyId, "configured input");

            if (!configuredCurrencyIds.add(currencyId)) {
                throw new IllegalArgumentException(
                        "Duplicate input currency UUID: " + currencyId
                );
            }
            if (EVE_ID.equals(currencyId)) {
                throw new IllegalArgumentException(
                        "Evelyn IOU cannot be configured as a burner input"
                );
            }
            if (!SOLANA_ID.equals(currencyId)) {
                requireUniqueSolanaMint(currencyId);
            }
            validatedInputs.add(minimumReserve);
        }
        return List.copyOf(validatedInputs);
    }

    private CurrencyType requireCanonicalCurrency(
            UUID currencyId,
            String purpose
    ) {
        try {
            CurrencyType canonicalCurrency = currencyIdentityService.resolve(
                    Objects.requireNonNull(currencyId, "currencyId")
            );
            if (canonicalCurrency == null) {
                throw new IllegalArgumentException(
                        "CIS returned <null> for " + purpose + " UUID "
                                + currencyId
                );
            }
            if (!currencyId.equals(canonicalCurrency.id())) {
                throw new IllegalArgumentException(
                        "CIS returned canonical currency UUID '"
                                + canonicalCurrency.id()
                                + "' while resolving "
                                + currencyId
                );
            }
            return canonicalCurrency;
        } catch (IllegalArgumentException exception) {
            throw new IllegalArgumentException(
                    "Unknown or invalid " + purpose + " AssetAZ UUID: "
                            + currencyId,
                    exception
            );
        }
    }

    private ΩSPLMintAddressΩ requireUniqueSolanaMint(UUID currencyId) {
        Set<ExternalCurrencyReference> references =
                currencyIdentityService.findExternalReferences(currencyId);
        if (references == null) {
            throw new IllegalArgumentException(
                    "CIS returned no external-reference set for UUID "
                            + currencyId
            );
        }

        ExternalCurrencyReference matchingReference = null;
        int matchingReferenceCount = 0;
        for (ExternalCurrencyReference reference : references) {
            if (reference != null
                    && SOLANA_MINT_NAMESPACE.equals(reference.namespace())) {
                matchingReference = reference;
                matchingReferenceCount++;
            }
        }
        if (matchingReferenceCount != 1) {
            throw new IllegalArgumentException(
                    "AssetAZ currency UUID "
                            + currencyId
                            + " must have exactly one "
                            + SOLANA_MINT_NAMESPACE
                            + " external reference, but has "
                            + matchingReferenceCount
            );
        }

        String mintAddress = matchingReference.externalId();
        if (mintAddress == null || mintAddress.isBlank()) {
            throw new IllegalArgumentException(
                    "AssetAZ currency UUID "
                            + currencyId
                            + " has a blank "
                            + SOLANA_MINT_NAMESPACE
                            + " external ID"
            );
        }
        return mintAddress;
    }

    private void warnSkippedInput(
            UUID currencyId,
            String unavailableData,
            Throwable exception
    ) {
        String failureType = exception == null
                ? "unavailable"
                : exception.getClass().getName();
        log.warn(
                "Skipping Evelyn IOU buyback input: currencyId={}, "
                        + "unavailableData={}, failureType={}",
                currencyId,
                unavailableData,
                failureType
        );
    }

    private static Duration requirePositiveDuration(
            Duration duration,
            String parameterName
    ) {
        Objects.requireNonNull(duration, parameterName);
        if (duration.isZero() || duration.isNegative()) {
            throw new IllegalArgumentException(
                    parameterName + " must be greater than zero"
            );
        }
        return duration;
    }

    private synchronized boolean isStopping() {
        return stopping;
    }

    private void resetProcessLocalState() {
        pendingBurn = null;
        swapsSuspended = false;
        burnsSuspended = false;
    }

    private static final Logger log = LoggerFactory.getLogger(
            EvelynIOUBurnerServiceImpl.class
    );
    private static final ΩSPLMintAddressΩ EVELYN_IOU_MINT =
            "meveYG2iXYSkgSUn1T1uxcthH1EGMZdRHGgCntXZA3Y";
    private static final ΩSPLMintAddressΩ CANONICAL_SOLANA_JUPITER_MINT =
            "So11111111111111111111111111111111111111112";
    private static final int MAXIMUM_SLIPPAGE_BPS = 10000;
    private static final ΩamountDecimalsΩ NATIVE_SOLANA_DECIMALS = 9;
    private static final ΩamountDecimalsΩ MAXIMUM_SPL_TOKEN_DECIMALS = 255;
    private static final long WORKER_TERMINATION_TIMEOUT_SECONDS = 10;
    private static final String WORKER_THREAD_NAME =
            "evelyn-iou-buyback-and-burner";

    private final SolanaWallet burnerWallet;
    private final JupiterSwapService swapService;
    private final SolanaBlockChain solanaBlockChain;
    private final TickerService tickerService;
    private final CurrencyIdentityService currencyIdentityService;
    private final BoundNotificationService notificationService;
    private final List<MoneyAmount> inputCurrencyMinimumReserves;
    private final ΩUSDCAmountΩ maximumSwapAmountInUSDC;
    private final int maximumSlippageBps;
    private final ΩmilliSecondsΩ maximumPriceAge;
    private final Duration maximumPriceAgeDuration;
    private final Duration burnFinalizationTimeout;
    private final long iterationIntervalNanos;
    private final CurrencyType canonicalUsdc;
    private final TradingPair eveUsdtTradingPair;

    private ScheduledExecutorService worker;
    private boolean stopping;
    private boolean swapsSuspended;
    private boolean burnsSuspended;
    private PendingBurn pendingBurn;

    private record PendingBurn(
            ΩSolanaTransactionSignatureΩ transactionSignature,
            ΩAmountΩ eveAmount,
            ΩAmountΩ dollarValue
    ) {
    }
}
