"""Minswap DEX Module."""

from dataclasses import dataclass
from hashlib import sha3_256
from typing import ClassVar
from typing import List
from typing import Union

from pycardano import Address
from pycardano import Datum
from pycardano import PlutusData
from pycardano import PlutusV1Script
from pycardano import PlutusV2Script
from pycardano import TransactionId
from pycardano import TransactionInput
from pycardano import TransactionOutput
from pycardano import UTxO
from pycardano import Value
from pycardano import VerificationKeyHash

from charli3_dendrite.dataclasses.datums import AssetClass
from charli3_dendrite.dataclasses.datums import OrderDatum
from charli3_dendrite.dataclasses.datums import PlutusFullAddress
from charli3_dendrite.dataclasses.datums import PlutusNone
from charli3_dendrite.dataclasses.datums import PoolDatum
from charli3_dendrite.dataclasses.datums import ReceiverDatum
from charli3_dendrite.dataclasses.datums import _PlutusConstrWrapper
from charli3_dendrite.dataclasses.models import OrderType
from charli3_dendrite.dataclasses.models import PoolSelector
from charli3_dendrite.dexs.amm.amm_types import AbstractCommonStableSwapPoolState
from charli3_dendrite.dexs.amm.amm_types import AbstractConstantProductPoolState
from charli3_dendrite.dexs.amm.sundae import SundaeV3PlutusNone
from charli3_dendrite.dexs.amm.sundae import SundaeV3ReceiverDatumHash
from charli3_dendrite.dexs.amm.sundae import SundaeV3ReceiverInlineDatum
from charli3_dendrite.utility import Assets


@dataclass
class SwapExactIn(PlutusData):
    """Swap exact in order datum."""

    CONSTR_ID = 0
    desired_coin: AssetClass
    minimum_receive: int

    @classmethod
    def from_assets(cls, asset: Assets):
        """Parse an Assets object into a SwapExactIn datum."""
        assert len(asset) == 1

        return SwapExactIn(
            desired_coin=AssetClass.from_assets(asset),
            minimum_receive=asset.quantity(),
        )


@dataclass
class StableSwapExactIn(PlutusData):
    """Swap exact in order datum."""

    CONSTR_ID = 0
    input_coin: int
    output_coin: int
    minimum_receive: int

    @classmethod
    def from_assets(cls, in_assets: Assets, out_assets: Assets):
        """Parse an Assets object into a SwapExactIn datum."""
        assert len(in_assets) == 1
        assert len(out_assets) == 1

        merged = in_assets + out_assets
        if in_assets.unit() == merged.unit():
            input_coin = 0
            output_coin = 1
        else:
            input_coin = 1
            output_coin = 0

        return StableSwapExactIn(
            input_coin=input_coin,
            output_coin=output_coin,
            minimum_receive=out_assets.quantity(),
        )


@dataclass
class StableSwapDeposit(PlutusData):
    """Swap exact out order datum."""

    CONSTR_ID = 1
    expected_receive: int

    @classmethod
    def from_assets(cls, asset: Assets):
        """Parse an Assets object into a SwapExactOut datum."""
        assert len(asset) == 1

        return StableSwapDeposit(
            expected_receive=asset.quantity(),
        )


@dataclass
class StableSwapWithdraw(PlutusData):
    """Swap exact out order datum."""

    CONSTR_ID = 2
    expected_receive: List[int]

    @classmethod
    def from_assets(cls, asset: Assets):
        """Parse an Assets object into a SwapExactOut datum."""
        assert len(asset) == 2

        return StableSwapWithdraw(
            expected_receive=[asset.quantity(), asset.quantity(1)],
        )


@dataclass
class StableSwapWithdrawOneCoin(PlutusData):
    """Swap exact out order datum."""

    CONSTR_ID = 4
    coin: int
    expected_receive: int

    @classmethod
    def from_assets(cls, coin_index: int, asset: Assets):
        """Parse an Assets object into a SwapExactOut datum."""
        assert len(asset) == 1

        return StableSwapWithdrawOneCoin(
            expected_receive=[coin_index, asset.quantity()],
        )


@dataclass
class SwapExactOut(PlutusData):
    """Swap exact out order datum."""

    CONSTR_ID = 1
    desired_coin: AssetClass
    expected_receive: int

    @classmethod
    def from_assets(cls, asset: Assets):
        """Parse an Assets object into a SwapExactOut datum."""
        assert len(asset) == 1

        return SwapExactOut(
            desired_coin=AssetClass.from_assets(asset),
            expected_receive=asset.quantity(),
        )


@dataclass
class Deposit(PlutusData):
    """Swap exact in order datum."""

    CONSTR_ID = 2
    minimum_lp: int


@dataclass
class Withdraw(PlutusData):
    """Swap exact in order datum."""

    CONSTR_ID = 3
    min_asset_a: int
    min_asset_b: int


@dataclass
class ZapIn(PlutusData):
    """Swap exact in order datum."""

    CONSTR_ID = 4
    desired_coin: AssetClass
    minimum_lp: int


@dataclass
class MinswapOrderDatum(OrderDatum):
    """An order datum."""

    sender: PlutusFullAddress
    receiver: PlutusFullAddress
    receiver_datum_hash: Union[ReceiverDatum, PlutusNone]
    step: Union[
        SwapExactIn,
        SwapExactOut,
        Deposit,
        Withdraw,
        ZapIn,
        StableSwapExactIn,
        StableSwapDeposit,
        StableSwapWithdraw,
        StableSwapWithdrawOneCoin,
    ]
    batcher_fee: int
    deposit: int

    @classmethod
    def create_datum(
        cls,
        address_source: Address,
        in_assets: Assets,
        out_assets: Assets,
        batcher_fee: Assets,
        deposit: Assets,
        address_target: Address | None = None,
        datum_target: PlutusData | None = None,
    ):
        """Create an order datum."""
        full_address_source = PlutusFullAddress.from_address(address_source)
        step = SwapExactIn.from_assets(out_assets)

        if address_target is None:
            address_target = address_source
            datum_target = PlutusNone()
        elif datum_target is None:
            datum_target = PlutusNone()

        full_address_target = PlutusFullAddress.from_address(address_target)

        return cls(
            full_address_source,
            full_address_target,
            datum_target,
            step,
            batcher_fee.quantity(),
            deposit.quantity(),
        )

    def address_source(self) -> Address:
        """The source address."""
        return self.sender.to_address()

    def requested_amount(self) -> Assets:
        """The requested amount."""
        if isinstance(self.step, SwapExactIn):
            return Assets(
                {self.step.desired_coin.assets.unit(): self.step.minimum_receive},
            )
        elif isinstance(self.step, SwapExactOut):
            return Assets(
                {self.step.desired_coin.assets.unit(): self.step.expected_receive},
            )
        elif isinstance(self.step, Deposit):
            return Assets({"lp": self.step.minimum_lp})
        elif isinstance(self.step, Withdraw):
            return Assets(
                {"asset_a": self.step.min_asset_a, "asset_b": self.step.min_asset_a},
            )
        elif isinstance(self.step, ZapIn):
            return Assets({self.step.desired_coin.assets.unit(): self.step.minimum_lp})

    def order_type(self) -> OrderType | None:
        """The order type."""
        order_type = None
        if isinstance(self.step, (SwapExactIn, SwapExactOut, StableSwapExactIn)):
            order_type = OrderType.swap
        elif isinstance(self.step, (Deposit, StableSwapDeposit, ZapIn)):
            order_type = OrderType.deposit
        elif isinstance(
            self.step,
            (Withdraw, StableSwapWithdraw, StableSwapWithdrawOneCoin),
        ):
            order_type = OrderType.withdraw

        return order_type


@dataclass
class BoolFalse(PlutusData):
    CONSTR_ID = 0


@dataclass
class BoolTrue(PlutusData):
    CONSTR_ID = 1


@dataclass
class SAOSpecificAmount(PlutusData):
    CONSTR_ID = 0

    swap_amount: int


@dataclass
class SAOAll(PlutusData):
    CONSTR_ID = 1

    deducted_amount: int


@dataclass
class SwapAmountOption(PlutusData):
    CONSTR_ID = 0

    option: Union[SAOSpecificAmount, SAOAll]


@dataclass
class SwapExactInV2(PlutusData):
    """Swap exact in order datum."""

    CONSTR_ID = 0
    a_to_b_direction: Union[BoolTrue, BoolFalse]
    swap_amount_option: Union[SAOSpecificAmount, SAOAll]
    minimum_receive: int
    killable: Union[BoolTrue, BoolFalse]

    @classmethod
    def from_assets(cls, in_asset: Assets, out_asset: Assets) -> "SwapExactInV2":
        """Parse an Assets object into a SwapExactInV2 datum."""
        assert len(in_asset) == 1

        merged_assets = in_asset + out_asset

        direction = (
            BoolTrue() if in_asset.unit() == merged_assets.unit() else BoolFalse()
        )

        option = SAOSpecificAmount(swap_amount=in_asset.quantity())

        return cls(
            a_to_b_direction=direction,
            swap_amount_option=option,
            minimum_receive=out_asset.quantity(),
            killable=BoolFalse(),
        )


@dataclass
class StopLossV2(PlutusData):
    """Stop loss order datum."""

    CONSTR_ID = 1
    a_to_b_direction: Union[BoolTrue, BoolFalse]
    swap_amount_option: Union[SAOSpecificAmount, SAOAll]
    stop_loss_receive: int


@dataclass
class OCOV2(PlutusData):
    """OCO order datum."""

    CONSTR_ID = 2
    a_to_b_direction: Union[BoolTrue, BoolFalse]
    swap_amount_option: Union[SAOSpecificAmount, SAOAll]
    minimum_receive: int
    stop_loss_receive: int


@dataclass
class SwapExactOutV2(PlutusData):
    """Swap exact out order datum."""

    CONSTR_ID = 3
    a_to_b_direction: Union[BoolTrue, BoolFalse]
    swap_amount_option: Union[SAOSpecificAmount, SAOAll]
    expected_receive: int
    killable: Union[BoolTrue, BoolFalse]


@dataclass
class DepositV2(PlutusData):
    """DepositV2 order datum."""

    CONSTR_ID = 4
    deposit_amount_option: Datum
    minimum_lp: int
    killable: Union[BoolTrue, BoolFalse]


@dataclass
class WithdrawV2(PlutusData):
    """WithdrawV2 order datum."""

    CONSTR_ID = 5
    withdrawal_amount_option: Datum
    minimum_asset_a: int
    minimum_asset_b: int
    killable: Union[BoolTrue, BoolFalse]


@dataclass
class ZapOutV2(PlutusData):
    """ZapOutV2 order datum."""

    CONSTR_ID = 6
    a_to_b_direction: Union[BoolTrue, BoolFalse]
    withdrawal_amount_option: Datum
    minimum_receive: int
    killable: Union[BoolTrue, BoolFalse]


@dataclass
class PartialSwapV2(PlutusData):
    """PartialSwapV2 order datum."""

    CONSTR_ID = 7
    a_to_b_direction: Union[BoolTrue, BoolFalse]
    total_swap_amount: int
    io_ratio_numerator: int
    io_ratio_denominator: int
    hops: int
    minimum_swap_amount_required: int
    max_batcher_fee_each_time: int


@dataclass
class WithdrawImbalanceV2(PlutusData):
    """WithdrawImbalanceV2 order datum."""

    CONSTR_ID = 8
    withdrawal_amount_optino: Datum
    ratio_asset_a: int
    ratio_asset_b: int
    minimum_asset_a: int
    killable: Union[BoolTrue, BoolFalse]


@dataclass
class Route(PlutusData):
    """Route."""

    CONSTR_ID = 0

    pool: AssetClass
    a_to_b_direction: Union[BoolTrue, BoolFalse]


@dataclass
class SwapMultiRoutingV2(PlutusData):
    """SwapMultiRoutingV2 order datum."""

    CONSTR_ID = 9
    routings: List[Route]
    swap_amount_option: Union[SAOSpecificAmount, SAOAll]
    minimum_receive: int


@dataclass
class DonationV2(PlutusData):
    """SwapMultiRoutingV2 order datum."""

    CONSTR_ID = 10


@dataclass
class OAMSignature(PlutusData):
    """SwapMultiRoutingV2 order datum."""

    CONSTR_ID = 0

    pub_key_hash: bytes


@dataclass
class OAMSpend(PlutusData):
    """SwapMultiRoutingV2 order datum."""

    CONSTR_ID = 1

    script_hash: bytes


@dataclass
class OAMWithdraw(PlutusData):
    """SwapMultiRoutingV2 order datum."""

    CONSTR_ID = 2

    script_hash: bytes


@dataclass
class OAMMint(PlutusData):
    """SwapMultiRoutingV2 order datum."""

    CONSTR_ID = 3

    script_hash: bytes


@dataclass
class Expire(PlutusData):
    """SwapMultiRoutingV2 order datum."""

    CONSTR_ID = 0

    ttl: List[int]


@dataclass
class MinswapV2OrderDatum(OrderDatum):
    """An order datum."""

    owner: Union[OAMMint, OAMSignature, OAMSpend, OAMWithdraw]
    refund_address: PlutusFullAddress
    refund_datum_hash: Union[
        SundaeV3PlutusNone,
        SundaeV3ReceiverDatumHash,
        SundaeV3ReceiverInlineDatum,
    ]
    receiver_address: PlutusFullAddress
    receiver_datum_hash: Union[
        SundaeV3PlutusNone,
        SundaeV3ReceiverDatumHash,
        SundaeV3ReceiverInlineDatum,
    ]
    lp_asset: AssetClass
    step: Union[
        SwapExactInV2,
        StopLossV2,
        OCOV2,
        SwapExactOutV2,
        DepositV2,
        WithdrawV2,
        ZapOutV2,
        PartialSwapV2,
        WithdrawImbalanceV2,
        SwapMultiRoutingV2,
        DonationV2,
    ]
    max_batcher_fee: int
    expiration_setting: Union[PlutusNone, Expire]

    @classmethod
    def create_datum(
        cls,
        address_source: Address,
        in_assets: Assets,
        out_assets: Assets,
        batcher_fee: Assets,
        deposit: Assets,
        address_target: Address | None = None,
        datum_target: PlutusData | None = None,
    ):
        """Create an order datum."""
        full_address_source = PlutusFullAddress.from_address(address_source)
        step = SwapExactInV2.from_assets(in_asset=in_assets, out_asset=out_assets)

        if address_target is None:
            address_target = address_source
            datum_target = SundaeV3PlutusNone()
        elif datum_target is None:
            datum_target = SundaeV3PlutusNone()

        full_address_target = PlutusFullAddress.from_address(address_target)

        merged_assets = in_assets + out_assets

        if merged_assets.unit() == "lovelace":
            token_a = sha3_256(bytes.fromhex("")).digest()
        else:
            token_a = sha3_256(bytes.fromhex(merged_assets.unit())).digest()
        token_b = sha3_256(bytes.fromhex(merged_assets.unit(1))).digest()
        pool_name = sha3_256(token_a + token_b).hexdigest()
        lp_asset = AssetClass.from_assets(
            Assets(
                **{
                    "f5808c2c990d86da54bfc97d89cee6efa20cd8461616359478d96b4c"
                    + pool_name: 0,
                },
            ),
        )

        return cls(
            owner=OAMSignature(address_source.payment_part.payload),
            refund_address=full_address_source,
            refund_datum_hash=datum_target,
            receiver_address=full_address_target,
            receiver_datum_hash=datum_target,
            lp_asset=lp_asset,
            step=step,
            max_batcher_fee=batcher_fee.quantity(),
            expiration_setting=PlutusNone(),
        )

    def address_source(self) -> Address:
        """The source address."""
        if isinstance(self.owner, OAMSignature):
            h = self.owner.pub_key_hash
        else:
            h = self.owner.script_hash

        return Address(payment_part=VerificationKeyHash(h))

    def requested_amount(self) -> Assets:
        """The requested amount."""
        if isinstance(self.step, SwapExactInV2):
            if isinstance(self.step.a_to_b_direction, BoolTrue):
                return Assets({"asset_b": self.step.minimum_receive})
            else:
                return Assets({"asset_a": self.step.minimum_receive})
        elif isinstance(self.step, SwapExactOutV2):
            if isinstance(self.step.a_to_b_direction, BoolTrue):
                return Assets({"asset_b": self.step.expected_receive})
            else:
                return Assets({"asset_a": self.step.expected_receive})
        elif isinstance(self.step, DepositV2):
            return Assets({"lp": self.step.minimum_lp})
        elif isinstance(self.step, WithdrawV2):
            return Assets(
                {
                    "asset_a": self.step.minimum_asset_a,
                    "asset_b": self.step.minimum_asset_b,
                },
            )
        elif isinstance(self.step, SwapMultiRoutingV2):
            if isinstance(self.step.routings[-1].a_to_b_direction, BoolTrue):
                return Assets({"asset_b": self.step.minimum_receive})
            else:
                return Assets({"asset_a": self.step.minimum_receive})
        else:
            return Assets({})

    def order_type(self) -> OrderType | None:
        """The order type."""
        order_type = None
        if isinstance(
            self.step,
            (
                SwapExactInV2,
                StopLossV2,
                OCOV2,
                SwapExactOutV2,
                PartialSwapV2,
                SwapMultiRoutingV2,
            ),
        ):
            order_type = OrderType.swap
        elif isinstance(self.step, (DepositV2, DonationV2)):
            order_type = OrderType.deposit
        elif isinstance(self.step, (WithdrawV2, ZapOutV2, WithdrawImbalanceV2)):
            order_type = OrderType.withdraw

        return order_type


@dataclass
class MinswapStableOrderDatum(MinswapOrderDatum):
    """MinSwap Stable Order Datum."""

    @classmethod
    def create_datum(
        cls,
        address_source: Address,
        in_assets: Assets,
        out_assets: Assets,
        batcher_fee: Assets,
        deposit: Assets,
        address_target: Address | None = None,
        datum_target: PlutusData | None = None,
    ):
        """Create an order datum."""
        full_address_source = PlutusFullAddress.from_address(address_source)
        step = StableSwapExactIn.from_assets(in_assets=in_assets, out_assets=out_assets)

        if address_target is None:
            address_target = address_source
            datum_target = PlutusNone()
        elif datum_target is None:
            datum_target = PlutusNone()

        full_address_target = PlutusFullAddress.from_address(address_target)

        return cls(
            full_address_source,
            full_address_target,
            datum_target,
            step,
            batcher_fee.quantity(),
            deposit.quantity(),
        )


@dataclass
class FeeDatumHash(PlutusData):
    """Fee datum hash."""

    CONSTR_ID = 0
    fee_hash: bytes


@dataclass
class FeeSwitchOn(PlutusData):
    """Fee switch on."""

    CONSTR_ID = 0
    address: PlutusFullAddress
    fee_to_datum_hash: PlutusNone


@dataclass
class _FeeSwitchWrapper(PlutusData):
    """Fee switch wrapper."""

    CONSTR_ID = 0
    fee_sharing: FeeSwitchOn


@dataclass
class MinswapPoolDatum(PoolDatum):
    """Pool Datum."""

    CONSTR_ID = 0

    asset_a: AssetClass
    asset_b: AssetClass
    total_liquidity: int
    root_k_last: int
    fee_sharing: Union[_FeeSwitchWrapper, PlutusNone]

    def pool_pair(self) -> Assets | None:
        """Return the asset pair associated with the pool."""
        return self.asset_a.assets + self.asset_b.assets


@dataclass
class OptionalInt(PlutusData):
    CONSTR_ID = 0

    value: int


@dataclass
class MinswapV2PoolDatum(PoolDatum):
    """Pool Datum."""

    CONSTR_ID = 0

    pool_batching_stake_credential: _PlutusConstrWrapper
    asset_a: AssetClass
    asset_b: AssetClass
    total_liquidity: int
    reserve_a: int
    reserve_b: int
    base_fee_a_numerator: int
    base_fee_b_numerator: int
    fee_sharing_numerator: Union[PlutusNone, OptionalInt]
    allow_dynamic_fee: Union[BoolTrue, BoolFalse]

    def pool_pair(self) -> Assets | None:
        """Return the asset pair associated with the pool."""
        return self.asset_a.assets + self.asset_b.assets


@dataclass
class MinswapStablePoolDatum(PlutusData):
    """Stable Pool Datum."""

    CONSTR_ID = 0

    balances: List[int]
    total_liquidity: int
    amp: int
    order_hash: bytes

    def pool_pair(self) -> Assets | None:
        """Return the asset pair associated with the pool."""
        raise NotImplementedError


@dataclass
class MinswapDJEDiUSDStablePoolDatum(MinswapStablePoolDatum):
    """Pool Datum."""

    CONSTR_ID = 0

    def pool_pair(self) -> Assets | None:
        """Return the asset pair associated with the pool."""
        return Assets(
            **{
                "8db269c3ec630e06ae29f74bc39edd1f87c819f1056206e879a1cd61446a65644d6963726f555344": 0,
                "f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b6988069555344": 0,
            },
        )


@dataclass
class MinswapDJEDUSDCStablePoolDatum(MinswapStablePoolDatum):
    """Pool Datum."""

    CONSTR_ID = 0

    def pool_pair(self) -> Assets | None:
        """Return the asset pair associated with the pool."""
        return Assets(
            **{
                "8db269c3ec630e06ae29f74bc39edd1f87c819f1056206e879a1cd61446a65644d6963726f555344": 0,
                "25c5de5f5b286073c593edfd77b48abc7a48e5a4f3d4cd9d428ff93555534443": 0,
            },
        )


@dataclass
class MinswapDJEDUSDMStablePoolDatum(MinswapStablePoolDatum):
    """Pool Datum."""

    CONSTR_ID = 0

    def pool_pair(self) -> Assets | None:
        return Assets(
            **{
                "8db269c3ec630e06ae29f74bc39edd1f87c819f1056206e879a1cd61446a65644d6963726f555344": 0,
                "c48cbb3d5e57ed56e276bc45f99ab39abe94e6cd7ac39fb402da47ad0014df105553444d": 0,
            },
        )


@dataclass
class MinswapiUSDUSDMStablePoolDatum(MinswapStablePoolDatum):
    """Pool Datum."""

    CONSTR_ID = 0

    def pool_pair(self) -> Assets | None:
        return Assets(
            **{
                "c48cbb3d5e57ed56e276bc45f99ab39abe94e6cd7ac39fb402da47ad0014df105553444d": 0,
                "f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b6988069555344": 0,
            },
        )


@dataclass
class MinswapUSDAUSDMStablePoolDatum(MinswapStablePoolDatum):
    """Pool Datum."""

    CONSTR_ID = 0

    def pool_pair(self) -> Assets | None:
        return Assets(
            **{
                "c48cbb3d5e57ed56e276bc45f99ab39abe94e6cd7ac39fb402da47ad0014df105553444d": 0,
                "fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae45655534441": 0,
            },
        )


@dataclass
class MinswapiUSDUSDCStablePoolDatum(MinswapStablePoolDatum):
    """Pool Datum."""

    CONSTR_ID = 0

    def pool_pair(self) -> Assets | None:
        return Assets(
            **{
                "25c5de5f5b286073c593edfd77b48abc7a48e5a4f3d4cd9d428ff93555534443": 0,
                "f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b6988069555344": 0,
            },
        )


class MinswapCPPState(AbstractConstantProductPoolState):
    """Minswap Constant Product Pool State."""

    fee: int = 30
    _batcher = Assets(lovelace=900000)
    _deposit = Assets(lovelace=2000000)
    _stake_address: ClassVar[Address] = [
        Address.from_primitive(
            "addr1zxn9efv2f6w82hagxqtn62ju4m293tqvw0uhmdl64ch8uw6j2c79gy9l76sdg0xwhd7r0c0kna0tycz4y5s6mlenh8pq6s3z70",
        ),
        Address.from_primitive(
            "addr1wxn9efv2f6w82hagxqtn62ju4m293tqvw0uhmdl64ch8uwc0h43gt",
        ),
    ]

    @classmethod
    def dex(cls) -> str:
        return "Minswap"

    @classmethod
    def order_selector(self) -> list[str]:
        return [s.encode() for s in self._stake_address]

    @classmethod
    def pool_selector(cls) -> PoolSelector:
        return PoolSelector(
            addresses=["addr1w8snz7c4974vzdpxu65ruphl3zjdvtxw8strf2c2tmqnxzgusf9xw"],
            assets=[
                "13aa2accf2e1561723aa26871e071fdf32c867cff7e7d50ad470d62f4d494e53574150",
            ],
        )

    @property
    def swap_forward(self) -> bool:
        return True

    @property
    def stake_address(self) -> Address:
        return self._stake_address[0]

    @classmethod
    def order_datum_class(self) -> type[MinswapOrderDatum]:
        return MinswapOrderDatum

    @classmethod
    def script_class(self) -> type[MinswapOrderDatum]:
        return PlutusV1Script

    @classmethod
    def pool_datum_class(self) -> type[MinswapPoolDatum]:
        return MinswapPoolDatum

    # def batcher_fee(
    #     self,
    #     in_assets: Assets | None = None,
    #     out_assets: Assets | None = None,
    #     extra_assets: Assets | None = None,
    # ) -> Assets:
    #     """Batcher fee.

    #     For Minswap, the batcher fee decreases linearly from 2.0 ADA to 1.5 ADA as the
    #     MIN in the input assets from 0 - 50,000 MIN.
    #     """
    #     MIN = "29d222ce763455e3d7a09a665ce554f00ac89d2e99a1a83d267170c64d494e"
    #     if extra_assets is not None and MIN in extra_assets:
    #         fee_reduction = min(extra_assets[MIN] // 10**5, 500000)
    #     else:
    #         fee_reduction = 0
    #     return self._batcher - Assets(lovelace=fee_reduction)

    @property
    def pool_id(self) -> str:
        """A unique identifier for the pool."""
        return self.pool_nft.unit()

    @classmethod
    def pool_policy(cls) -> list[str]:
        return ["0be55d262b29f564998ff81efe21bdc0022621c12f15af08d0f2ddb1"]

    @classmethod
    def lp_policy(cls) -> list[str]:
        return ["e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d86"]

    @classmethod
    def dex_policy(cls) -> list[str]:
        return ["13aa2accf2e1561723aa26871e071fdf32c867cff7e7d50ad470d62f"]


class MinswapV2CPPState(AbstractConstantProductPoolState):
    """Minswap Constant Product Pool State."""

    fee: int | list[int] = [30, 30]
    _batcher = Assets(lovelace=700000)
    _deposit = Assets(lovelace=2000000)
    _stake_address: ClassVar[Address] = [
        Address.from_primitive(
            "addr1w8p79rpkcdz8x9d6tft0x0dx5mwuzac2sa4gm8cvkw5hcnqst2ctf",
        ),
    ]
    _reference_utxo: ClassVar[UTxO | None] = None

    @classmethod
    def dex(cls) -> str:
        return "MinswapV2"

    @classmethod
    def order_selector(self) -> list[str]:
        return [s.encode() for s in self._stake_address]

    @classmethod
    def pool_selector(cls) -> PoolSelector:
        return PoolSelector(
            addresses=["addr1w84q0denmyep98ph3tmzwsmw0j7zau9ljmsqx6a4rvaau6ca7j5v4"],
            assets=[
                "f5808c2c990d86da54bfc97d89cee6efa20cd8461616359478d96b4c4d5350",
            ],
        )

    @property
    def swap_forward(self) -> bool:
        return True

    @property
    def stake_address(self) -> Address:
        return self._stake_address[0]

    @classmethod
    def order_datum_class(self) -> type[MinswapV2OrderDatum]:
        return MinswapV2OrderDatum

    @classmethod
    def default_script_class(cls) -> type[PlutusV1Script] | type[PlutusV2Script]:
        return PlutusV2Script

    @classmethod
    def script_class(self) -> type[PlutusV2Script]:
        return PlutusV2Script

    @classmethod
    def pool_datum_class(self) -> type[MinswapV2PoolDatum]:
        return MinswapV2PoolDatum

    # def batcher_fee(
    #     self,
    #     in_assets: Assets | None = None,
    #     out_assets: Assets | None = None,
    #     extra_assets: Assets | None = None,
    # ) -> Assets:
    #     """Batcher fee.

    #     For Minswap, the batcher fee decreases linearly from 2.0 ADA to 1.5 ADA as the
    #     MIN in the input assets from 0 - 50,000 MIN.
    #     """
    #     MIN = "29d222ce763455e3d7a09a665ce554f00ac89d2e99a1a83d267170c64d494e"
    #     if extra_assets is not None and MIN in extra_assets:
    #         fee_reduction = min(extra_assets[MIN] // 10**5, 500000)
    #     else:
    #         fee_reduction = 0
    #     return self._batcher - Assets(lovelace=fee_reduction)

    @property
    def pool_id(self) -> str:
        """A unique identifier for the pool."""
        return self.lp_tokens.unit()

    @classmethod
    def lp_policy(cls) -> list[str]:
        return ["f5808c2c990d86da54bfc97d89cee6efa20cd8461616359478d96b4c"]

    @classmethod
    def dex_policy(cls) -> list[str]:
        return ["f5808c2c990d86da54bfc97d89cee6efa20cd8461616359478d96b4c4d5350"]

    @classmethod
    def post_init(cls, values):
        super().post_init(values)

        datum = MinswapV2PoolDatum.from_cbor(values["datum_cbor"])

        assets = values["assets"]
        assets.root[assets.unit()] = datum.reserve_a
        assets.root[assets.unit(1)] = datum.reserve_b

        values["fee"] = [datum.base_fee_a_numerator, datum.base_fee_b_numerator]

    @classmethod
    def reference_utxo(cls) -> UTxO | None:
        """Retrieve the reference UTxO for the Spectrum DEX.

        This method checks if the reference UTxO is already set. If not, it retrieves
        the script bytes from the stake address and sets the reference UTxO.

        Returns:
            UTxO | None: The reference UTxO if available, otherwise None.
        """
        if cls._reference_utxo is None:
            cls._reference_utxo = UTxO(
                input=TransactionInput(
                    transaction_id=TransactionId(
                        bytes.fromhex(
                            "cf4ecddde0d81f9ce8fcc881a85eb1f8ccdaf6807f03fea4cd02da896a621776",
                        ),
                    ),
                    index=0,
                ),
                output=TransactionOutput(
                    address=Address.decode(
                        "addr1qyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzj2c79gy9l76sdg0xwhd7r0c0kna0tycz4y5s6mlenh8pq6a0h00",
                    ),
                    amount=Value(coin=12486070),
                    script=PlutusV2Script(
                        bytes.fromhex(
                            " ",
                        ),
                    ),
                ),
            )

        return cls._reference_utxo


class MinswapDJEDiUSDStableState(AbstractCommonStableSwapPoolState, MinswapCPPState):
    """Minswap DJED/iUSD Stable State."""

    fee: float = 1
    _batcher = Assets(lovelace=600000)
    _deposit = Assets(lovelace=2000000)
    _stake_address: ClassVar[Address] = [
        Address.from_primitive(
            "addr1w9xy6edqv9hkptwzewns75ehq53nk8t73je7np5vmj3emps698n9g",
        ),
    ]

    @classmethod
    def order_datum_class(cls) -> type[MinswapStableOrderDatum]:
        return MinswapStableOrderDatum

    def get_amount_out(
        self,
        asset: Assets,
        precise: bool = True,
    ) -> tuple[Assets, float]:
        out_asset, slippage = super().get_amount_out(
            asset=asset,
            precise=precise,
            fee_on_input=False,
        )

        return out_asset, slippage

    def get_amount_in(
        self,
        asset: Assets,
        precise: bool = True,
    ) -> tuple[Assets, float]:
        in_asset, slippage = super().get_amount_in(
            asset=asset,
            precise=precise,
            fee_on_input=False,
        )

        return in_asset, slippage

    @classmethod
    def post_init(cls, values: dict[str, ...]):
        """Post initialization checks.

        Args:
            values: The pool initialization parameters
        """
        super().post_init(values)
        assets = values["assets"]

        datum = cls.pool_datum_class().from_cbor(values["datum_cbor"])

        assets.root[assets.unit()] = datum.balances[0]
        assets.root[assets.unit(1)] = datum.balances[1]

        return values

    @property
    def amp(self) -> int:
        return self.pool_datum.amp

    @classmethod
    def pool_selector(cls) -> PoolSelector:
        return PoolSelector(
            addresses=["addr1wy7kkcpuf39tusnnyga5t2zcul65dwx9yqzg7sep3cjscesx2q5m5"],
            assets=[
                "5d4b6afd3344adcf37ccef5558bb87f522874578c32f17160512e398444a45442d695553442d534c50",
            ],
        )

    @classmethod
    def pool_datum_class(self) -> type[MinswapDJEDiUSDStablePoolDatum]:
        return MinswapDJEDiUSDStablePoolDatum

    @property
    def pool_id(self) -> str:
        """A unique identifier for the pool."""
        return self.pool_nft.unit()

    @classmethod
    def pool_policy(cls) -> list[str]:
        return [
            "5d4b6afd3344adcf37ccef5558bb87f522874578c32f17160512e398444a45442d695553442d534c50",
        ]

    @classmethod
    def lp_policy(cls) -> list[str] | None:
        return None

    @classmethod
    def dex_policy(cls) -> list[str] | None:
        return None


class MinswapDJEDUSDCStableState(MinswapDJEDiUSDStableState):
    """Minswap DJED/USDC Stable State."""

    asset_mulitipliers: ClassVar[list[int]] = [1, 100]

    _stake_address: ClassVar[Address] = [
        Address.from_primitive(
            "addr1w93d8cuht3hvqt2qqfjqgyek3gk5d6ss2j93e5sh505m0ng8cmze2",
        ),
    ]

    @classmethod
    def pool_selector(cls) -> PoolSelector:
        return PoolSelector(
            addresses=["addr1wx8d45xlfrlxd7tctve8xgdtk59j849n00zz2pgyvv47t8sxa6t53"],
            assets=[
                "d97fa91daaf63559a253970365fb219dc4364c028e5fe0606cdbfff9555344432d444a45442d534c50",
            ],
        )

    @classmethod
    def pool_datum_class(self) -> type[MinswapDJEDUSDCStablePoolDatum]:
        return MinswapDJEDUSDCStablePoolDatum

    @classmethod
    def pool_policy(cls) -> list[str]:
        return [
            "d97fa91daaf63559a253970365fb219dc4364c028e5fe0606cdbfff9555344432d444a45442d534c50",
        ]


class MinswapDJEDUSDMStableState(MinswapDJEDiUSDStableState):
    _stake_address: ClassVar[Address] = [
        Address.from_primitive(
            "addr1wxr9ppdymqgw6g0hvaaa7wc6j0smwh730ujx6lczgdynehsguav8d",
        ),
    ]

    @classmethod
    def pool_selector(cls) -> PoolSelector:
        return PoolSelector(
            addresses=["addr1wxxdvtj6y4fut4tmu796qpvy2xujtd836yg69ahat3e6jjcelrf94"],
            assets=[
                "07b0869ed7488657e24ac9b27b3f0fb4f76757f444197b2a38a15c3c444a45442d5553444d2d534c50",
            ],
        )

    @classmethod
    def pool_datum_class(self) -> type[MinswapDJEDUSDMStablePoolDatum]:
        return MinswapDJEDUSDMStablePoolDatum

    @classmethod
    def pool_policy(cls) -> list[str]:
        return [
            "07b0869ed7488657e24ac9b27b3f0fb4f76757f444197b2a38a15c3c444a45442d5553444d2d534c50",
        ]


class MinswapiUSDUSDMStableState(MinswapDJEDiUSDStableState):
    _stake_address: ClassVar[Address] = [
        Address.from_primitive(
            "addr1wxtv9k2lcum5pmcc4wu44a5tufulszahz84knff87wcawycez9lug",
        ),
    ]

    @classmethod
    def pool_selector(cls) -> PoolSelector:
        return PoolSelector(
            addresses=["addr1w9520fyp6g3pjwd0ymfy4v2xka54ek6ulv4h8vce54zfyfcm2m0sm"],
            assets=[
                "96402c6f5e7a04f16b4d6f500ab039ff5eac5d0226d4f88bf5523ce85553444d2d695553442d534c50",
            ],
        )

    @classmethod
    def pool_datum_class(self) -> type[MinswapiUSDUSDMStablePoolDatum]:
        return MinswapiUSDUSDMStablePoolDatum

    @classmethod
    def pool_policy(cls) -> list[str]:
        return [
            "96402c6f5e7a04f16b4d6f500ab039ff5eac5d0226d4f88bf5523ce85553444d2d695553442d534c50",
        ]


class MinswapUSDAUSDMStableState(MinswapDJEDiUSDStableState):
    fee: float = 5
    _stake_address: ClassVar[Address] = [
        Address.from_primitive(
            "addr1w8cafpjmeer4j8t8aseqayhwkf4ezuufue0clvfthxecsacv83rt0",
        ),
    ]

    @classmethod
    def pool_selector(cls) -> PoolSelector:
        return PoolSelector(
            addresses=["addr1wywdvw0qwv2n97e8y5jsfqq3qryu6re3gxwqcc7fzscpwugxz5dwe"],
            assets=[
                "a0d806e67be578911ca39260cff5eaa6eb06f9f4165ccd570282f5055553444d2d555344412d534c50",
            ],
        )

    @classmethod
    def pool_datum_class(self) -> type[MinswapUSDAUSDMStablePoolDatum]:
        return MinswapUSDAUSDMStablePoolDatum

    @classmethod
    def pool_policy(cls) -> list[str]:
        return [
            "a0d806e67be578911ca39260cff5eaa6eb06f9f4165ccd570282f5055553444d2d555344412d534c50",
        ]


class MinswapiUSDUSDCStableState(MinswapDJEDiUSDStableState):
    fee: float = 10
    asset_mulitipliers: ClassVar[list[int]] = [1, 100]

    _stake_address: ClassVar[Address] = [
        Address.from_primitive(
            "addr1wy42rt3rdptdaa2lwlntkx49ksuqrmqqjlu7pf5l5f8upmgj3gq2m",
        ),
    ]

    @classmethod
    def pool_selector(cls) -> PoolSelector:
        return PoolSelector(
            addresses=["addr1wx4w03kq5tfhaad2fmglefgejj0anajcsvvg88w96lrmylc7mx5rm"],
            assets=[
                "739150a2612da82e16adc2a3a1f88b256202d8415df0c5b7a2ff93fb555344432d695553442d302e312d534c50",
            ],
        )

    @classmethod
    def pool_datum_class(self) -> type[MinswapiUSDUSDCStablePoolDatum]:
        return MinswapiUSDUSDCStablePoolDatum

    @classmethod
    def pool_policy(cls) -> list[str]:
        return [
            "739150a2612da82e16adc2a3a1f88b256202d8415df0c5b7a2ff93fb555344432d695553442d302e312d534c50",
        ]
