package com.r35157.jupiterperpsalarm.impl.ref;

import com.r35157.cryptowallet.solana.SolanaWallet;
import com.r35157.libs.jupiter.JupiterTransactionOutcomeException;
import com.r35157.libs.jupiter.perps.JupiterPerpsPosition;
import com.r35157.libs.jupiter.perps.JupiterPerpsService;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.Objects;

public final class JupiterPerpsPositionDecreaseAlarmAction
        implements ConfiguredAlarmAction {
    public JupiterPerpsPositionDecreaseAlarmAction(
            JupiterPerpsPositionDecreaseAlarmActionConfiguration configuration,
            SolanaWallet wallet,
            JupiterPerpsService jupiter,
            AlarmVariableResolver variableResolver
    ) {
        this.configuration = Objects.requireNonNull(
                configuration,
                "configuration"
        );
        this.wallet = Objects.requireNonNull(wallet, "wallet");
        this.jupiter = Objects.requireNonNull(jupiter, "jupiter");
        this.variableResolver = Objects.requireNonNull(
                variableResolver,
                "variableResolver"
        );
    }

    @Override
    public String getConfigurationFileName() {
        return CONFIGURATION_FILE_NAME;
    }

    @Override
    public void trigger(OraclePrice price, ResolvedPriceAlarm alarm) {
        JupiterPerpsPositionDecreaseAlarmActionConfiguration.PositionDecrease
                definition =
                configuration.positionDecreases().get(alarm.id());
        if (definition == null) {
            return;
        }

        try {
            JupiterPerpsPositionDecreaseAlarmActionConfiguration
                    .ResolvedPositionDecrease positionDecrease =
                    definition.resolve(variableResolver);

            JupiterPerpsPosition position =
                    findOpenPosition(positionDecrease);

            if (!isPositionSizeOk(position, positionDecrease)) {
                return;
            }

            ΩSolanaTransactionSignatureΩ signature =
                    jupiter.executePositionDecrease(
                            position.positionAccount(),
                            positionDecrease.receiveToken()
                                    .receiveTokenMint(),
                            positionDecrease.sizeUsdDelta(),
                            positionDecrease.maxSlippageBps()
                    );

            System.out.println(
                    "Position Decrease Signature: " + signature
            );
        } catch (JupiterTransactionOutcomeException e) {
            reportTransactionOutcome(e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println(
                    "Jupiter Perps position decrease was interrupted"
            );
        } catch(Exception e) {
            System.err.println("EXCEPTION: " + e.getMessage());
        }
    }

    private static void reportTransactionOutcome(
            JupiterTransactionOutcomeException exception
    ) {
        switch (exception.outcome().status()) {
            case FAILED -> System.err.println(
                    "Jupiter Perps position decrease failed on-chain: "
                            + "transaction "
                            + exception.transactionSignature()
                            + ", confirmation slot "
                            + exception.outcome().slot()
                            + ", Solana error "
                            + exception.outcome().failureDetails()
            );
            case TIMED_OUT -> System.err.println(
                    "Jupiter Perps position decrease transaction "
                            + exception.transactionSignature()
                            + " has an unknown on-chain outcome after timeout; "
                            + "no equivalent transaction was automatically "
                            + "resubmitted"
            );
            case SUCCEEDED -> throw new IllegalStateException(
                    "A successful transaction outcome is not exceptional"
            );
        }
    }

    private JupiterPerpsPosition findOpenPosition(
            JupiterPerpsPositionDecreaseAlarmActionConfiguration
                    .ResolvedPositionDecrease positionDecrease
    ) throws IOException, InterruptedException {
        JupiterPerpsPosition matchingPosition = null;

        for (JupiterPerpsPosition position
                : jupiter.getOpenPositions(wallet.getAddress())) {
            if (!position.tradedTokenMint().equals(
                    positionDecrease.asset().tradedTokenMint()
            )) {
                continue;
            }

            if (position.direction() != positionDecrease.direction()) {
                continue;
            }

            if (matchingPosition != null) {
                throw new IllegalStateException(
                        "Found multiple open "
                                + positionDecrease.asset()
                                + " "
                                + positionDecrease.direction()
                                + " positions for wallet "
                                + wallet.getAddress()
                );
            }

            matchingPosition = position;
        }

        if (matchingPosition == null) {
            throw new IllegalStateException(
                    "No open "
                            + positionDecrease.asset()
                            + " "
                            + positionDecrease.direction()
                            + " position found for wallet "
                            + wallet.getAddress()
            );
        }

        return matchingPosition;
    }

    private boolean isPositionSizeOk(
            JupiterPerpsPosition position,
            JupiterPerpsPositionDecreaseAlarmActionConfiguration
                    .ResolvedPositionDecrease positionDecrease
    ) {
        ΩUSDCAmountΩ currentPositionSize = position.positionSize();
        ΩUSDCAmountΩ decreaseSizeUsd = positionDecrease.sizeUsdDelta();
        ΩUSDCAmountΩ reservePositionSizeUsdLimit =
                configuration.reservePositionSizeUsdLimit();

        ΩUSDCAmountΩ remainingPositionSize =
                currentPositionSize.subtract(decreaseSizeUsd);

        if (remainingPositionSize.compareTo(
                reservePositionSizeUsdLimit
        ) < 0) {
            System.err.println(
                    "Position Decrease of "
                            + decreaseSizeUsd
                            + " USD skipped: Position size is "
                            + currentPositionSize
                            + " USD and would become "
                            + remainingPositionSize
                            + " USD, which is below the "
                            + reservePositionSizeUsdLimit
                            + " USD reserve."
            );
            return false;
        }

        return true;
    }

    private final JupiterPerpsPositionDecreaseAlarmActionConfiguration
            configuration;
    private final SolanaWallet wallet;
    private final JupiterPerpsService jupiter;
    private final AlarmVariableResolver variableResolver;

    static final String CONFIGURATION_FILE_NAME =
            "alarmaction_JupiterPerpsPositionDecreaseAlarmAction.conf";
}
