package com.r35157.libs.jupiter.perps.impl.anchoridl;

import com.r35157.libs.jupiter.perps.JupiterPerpsPositionDirection;
import com.r35157.libs.solana.SolanaAccountInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Base64;

import static com.r35157.libs.jupiter.perps.impl.anchoridl.DecodingPrimitives.*;

class AnchorIdlJupiterPerpsPositionDecoder {

    @Nullable JupiterPerpsPositionInfo decode(
            @NotNull ΩJupiterPerpsPositionInfoEncodedΩ encodedPerpsPositionInfo
    ) {
        ΩJupiterPerpsPostionIdlΩ idl = decodeIdl(encodedPerpsPositionInfo);

        JupiterPerpsPositionDirection direction = decodeDirection(idl);

        ΩjupiterRawEntryPriceUsdΩ rawEntryPrice = ByteBuffer
                .wrap(idl, PRICE_OFFSET, U64_LENGTH)
                .order(ByteOrder.LITTLE_ENDIAN)
                .getLong();

        ΩUSDCPriceΩ entryPrice = ΩUSDCPriceΩ
                .valueOf(rawEntryPrice)
                .movePointLeft(6);

        ΩjupiterRawCollateralUsdΩ rawCollateralUsd = ByteBuffer
                .wrap(idl, COLLATERAL_USD_OFFSET, U64_LENGTH)
                .order(ByteOrder.LITTLE_ENDIAN)
                .getLong();

        ΩUSDCAmountΩ collateral = ΩUSDCAmountΩ
                .valueOf(rawCollateralUsd)
                .movePointLeft(6);

        ΩjupiterRawSizeUsdΩ rawSizeUsd = ByteBuffer
                .wrap(idl, SIZE_USD_OFFSET, U64_LENGTH)
                .order(ByteOrder.LITTLE_ENDIAN)
                .getLong();

        ΩUSDCAmountΩ positionSize = ΩUSDCAmountΩ
                .valueOf(rawSizeUsd)
                .movePointLeft(6);

        ΩJupiterPerpsCustodyAccountAddressΩ custodyAccountAddress =
                decodePublicKey(idl, CUSTODY_OFFSET);

        ΩJupiterPerpsCollateralCustodyAccountAddressΩ collateralCustodyAccountAddress =
                decodePublicKey(idl, COLLATERAL_CUSTODY_OFFSET);

        ΩJupiterCumulativeInterestSnapshotΩ cumulativeInterestSnapshot = decodeCumulativeInterestSnapshot(idl);

        JupiterPerpsPositionInfo posInfo = new JupiterPerpsPositionInfo(
                entryPrice,
                direction,
                positionSize,
                collateral,
                custodyAccountAddress,
                collateralCustodyAccountAddress,
                cumulativeInterestSnapshot
        );

        return posInfo;
    }

     private @NotNull ΩJupiterCumulativeInterestSnapshotΩ decodeCumulativeInterestSnapshot(ΩJupiterPerpsPostionIdlΩ idl) {
        byte[] bytes = readU128(idl, CUMULATIVE_INTEREST_SNAPSHOT_OFFSET);
         ΩJupiterCumulativeInterestSnapshotΩ cumulativeInterestSnapshot
                 = new ΩJupiterCumulativeInterestSnapshotΩ(1, bytes);

         return cumulativeInterestSnapshot;
    }

    ΩJupiterPerpsCustodyAccountAddressΩ decodeCustodyAccountAddress(SolanaAccountInfo accountInfo) {
        byte[] data = base64.decode(accountInfo.data());

        if (data.length < CUSTODY_OFFSET + PUBLIC_KEY_LENGTH) {
            String errorMsg = "Jupiter Perps position account data is too short: " + data.length;
            throw new IllegalArgumentException(errorMsg);
        }

        ΩJupiterPerpsCustodyAccountAddressΩ addr = decodePublicKey(data, CUSTODY_OFFSET);

        return addr;
    }

    private ΩJupiterPerpsPostionIdlΩ decodeIdl(ΩJupiterPerpsPositionInfoEncodedΩ encodedPerpsPositionInfo) {
        ΩJupiterPerpsPostionIdlΩ idl = base64.decode(encodedPerpsPositionInfo.data());

        // TODO: Check size correctly, as in AnchorIdlJupiterPerpsCustodyDecoder.decodeIdl()
        if (idl.length == 0) {
            String errorMsg = "Jupiter Perps position account data is too short: " + idl.length;
            throw new IllegalArgumentException(errorMsg);
        }

        return idl;
    }

    private JupiterPerpsPositionDirection decodeDirection(ΩJupiterPerpsPostionIdlΩ idl) {
        byte rawSide = idl[SIDE_OFFSET];

        // Jupiter Perps position side values are encoded as 1 = LONG, 2 = SHORT.
        JupiterPerpsPositionDirection direction = switch (rawSide) {
            case 1 -> JupiterPerpsPositionDirection.LONG;
            case 2 -> JupiterPerpsPositionDirection.SHORT;
            default -> throw new IllegalArgumentException(
                    "Unknown Jupiter Perps position side: " + rawSide
            );
        };

        return direction;
    }

    private static final int ANCHOR_DISCRIMINATOR_LENGTH = 8;
    private static final int PUBLIC_KEY_LENGTH = 32;
    private static final int I64_LENGTH = 8;
    private static final int SIDE_ENUM_LENGTH = 1;
    private static final int U64_LENGTH = 8;

    private static final int OWNER_OFFSET = ANCHOR_DISCRIMINATOR_LENGTH;
    private static final int POOL_OFFSET = OWNER_OFFSET + PUBLIC_KEY_LENGTH;
    private static final int CUSTODY_OFFSET = POOL_OFFSET + PUBLIC_KEY_LENGTH;
    private static final int COLLATERAL_CUSTODY_OFFSET = CUSTODY_OFFSET + PUBLIC_KEY_LENGTH;
    private static final int OPEN_TIME_OFFSET = COLLATERAL_CUSTODY_OFFSET + PUBLIC_KEY_LENGTH;
    private static final int UPDATE_TIME_OFFSET = OPEN_TIME_OFFSET + I64_LENGTH;
    private static final int SIDE_OFFSET = UPDATE_TIME_OFFSET + I64_LENGTH;
    private static final int PRICE_OFFSET = SIDE_OFFSET + SIDE_ENUM_LENGTH;
    private static final int SIZE_USD_OFFSET = PRICE_OFFSET + U64_LENGTH;
    private static final int COLLATERAL_USD_OFFSET = SIZE_USD_OFFSET + U64_LENGTH;
    private static final int REALISED_PNL_USD_OFFSET = COLLATERAL_USD_OFFSET + U64_LENGTH;
    private static final int CUMULATIVE_INTEREST_SNAPSHOT_OFFSET = REALISED_PNL_USD_OFFSET + I64_LENGTH;

    private static final Base64.Decoder base64 = Base64.getDecoder();
}