from enum import (
    Enum,
    auto
)

from rlp.sedes import (
    big_endian_int,
    Binary,
    CountableList,
    List,
    Text
)

from eth_rlp import (
    HashableRLP
)

from eth_utils.curried import (
    to_int,
    hexstr_if_str,
)

class StakingSettings:
    PRECISION = 18
    MAX_DECIMAL = 1000000000000000000

class Directive(Enum):      # https://github.com/harmony-one/sdk/blob/99a827782fabcd5f91f025af0d8de228956d42b4/packages/harmony-staking/src/stakingTransaction.ts#L120
    def _generate_next_value_(name, start, count, last_values):
        return count
    CreateValidator = auto()
    EditValidator = auto()
    Delegate = auto()
    Undelegate = auto()
    CollectRewards = auto()

FORMATTERS = {
    'directive': hexstr_if_str(to_int),     # delegatorAddress is already formatted before the call
    'nonce': hexstr_if_str(to_int),
    'gasPrice': hexstr_if_str(to_int),
    'gasLimit': hexstr_if_str(to_int),
    'chainId': hexstr_if_str(to_int),
}

class CollectRewards:
    @staticmethod
    def UnsignedChainId():
        class UnsignedChainId(HashableRLP):
            fields = (
                ('directive', big_endian_int),
                ('stakeMsg', CountableList(Binary.fixed_length(20, allow_empty=True))),
                ('nonce', big_endian_int),
                ('gasPrice', big_endian_int),
                ('gasLimit', big_endian_int),
                ('chainId', big_endian_int),
            )
        return UnsignedChainId

    @staticmethod
    def SignedChainId():
        class SignedChainId(HashableRLP):
            fields = CollectRewards.UnsignedChainId()._meta.fields[:-1] + ( # drop chainId
                ("v", big_endian_int),
                ("r", big_endian_int),
                ("s", big_endian_int),
            )
        return SignedChainId

    @staticmethod
    def Unsigned():
        class Unsigned(HashableRLP):
            fields = CollectRewards.UnsignedChainId()._meta.fields[:-1]  # drop chainId
        return Unsigned

    @staticmethod
    def Signed():
        class Signed(HashableRLP):
            fields = CollectRewards.Unsigned()._meta.fields[:-3] + (    # drop last 3 for raw.pop()
                ("v", big_endian_int),
                ("r", big_endian_int),
                ("s", big_endian_int),
            )
        return Signed

class DelegateOrUndelegate:
    @staticmethod
    def UnsignedChainId():
        class UnsignedChainId(HashableRLP):
            fields = (
                ('directive', big_endian_int),
                ('stakeMsg', List([Binary.fixed_length(20, allow_empty=True),Binary.fixed_length(20, allow_empty=True),big_endian_int],True)),
                ('nonce', big_endian_int),
                ('gasPrice', big_endian_int),
                ('gasLimit', big_endian_int),
                ('chainId', big_endian_int),
            )
        return UnsignedChainId

    @staticmethod
    def SignedChainId():
        class SignedChainId(HashableRLP):
            fields = DelegateOrUndelegate.UnsignedChainId()._meta.fields[:-1] + ( # drop chainId
                ("v", big_endian_int),
                ("r", big_endian_int),
                ("s", big_endian_int),
            )
        return SignedChainId

    @staticmethod
    def Unsigned():
        class Unsigned(HashableRLP):
            fields = DelegateOrUndelegate.UnsignedChainId()._meta.fields[:-1]  # drop chainId
        return Unsigned

    @staticmethod
    def Signed():
        class Signed(HashableRLP):
            fields = DelegateOrUndelegate.Unsigned()._meta.fields[:-3] + (    # drop last 3 for raw.pop()
                ("v", big_endian_int),
                ("r", big_endian_int),
                ("s", big_endian_int),
            )
        return Signed

class CreateValidator:
    @staticmethod
    def UnsignedChainId():
        class UnsignedChainId(HashableRLP):
            fields = (
                ('directive', big_endian_int),
                ('stakeMsg', List([                                                         # list with the following members
                            Binary.fixed_length(20, allow_empty=True),                      # validatorAddress
                            List([Text()]*5,True),                                          # description is Text of 5 elements
                            List([List([big_endian_int],True)]*3,True),                     # commission rate is made up of 3 integers in an array [ [int1], [int2], [int3] ]
                            big_endian_int,                                                 # min self delegation
                            big_endian_int,                                                 # max total delegation
                            CountableList(Binary.fixed_length(48, allow_empty=True)),       # bls-public-keys array of unspecified length, each key of 48
                            big_endian_int,                                                 # amount
                         ], True)),                                                         # strictly these number of elements
                ('nonce', big_endian_int),
                ('gasPrice', big_endian_int),
                ('gasLimit', big_endian_int),
                ('chainId', big_endian_int),
            )
        return UnsignedChainId

    @staticmethod
    def SignedChainId():
        class SignedChainId(HashableRLP):
            fields = CreateValidator.UnsignedChainId()._meta.fields[:-1] + ( # drop chainId
                ("v", big_endian_int),
                ("r", big_endian_int),
                ("s", big_endian_int),
            )
        return SignedChainId

    @staticmethod
    def Unsigned():
        class Unsigned(HashableRLP):
            fields = CreateValidator.UnsignedChainId()._meta.fields[:-1]  # drop chainId
        return Unsigned

    @staticmethod
    def Signed():
        class Signed(HashableRLP):
            fields = CreateValidator.Unsigned()._meta.fields[:-3] + (    # drop last 3 for raw.pop()
                ("v", big_endian_int),
                ("r", big_endian_int),
                ("s", big_endian_int),
            )
        return Signed

class EditValidator:
    @staticmethod
    def UnsignedChainId():
        class UnsignedChainId(HashableRLP):
            fields = (
                ('directive', big_endian_int),
                ('stakeMsg', List([                                                         # list with the following members
                            Binary.fixed_length(20, allow_empty=True),                      # validatorAddress
                            List([Text()]*5,True),                                          # description is Text of 5 elements
                            List([big_endian_int],True),                                    # new rate is in a list
                            big_endian_int,                                                 # min self delegation
                            big_endian_int,                                                 # max total delegation
                            Binary.fixed_length(48, allow_empty=True),                      # slot key to remove
                            Binary.fixed_length(48, allow_empty=True),                      # slot key to add
                         ], True)),                                                         # strictly these number of elements
                ('nonce', big_endian_int),
                ('gasPrice', big_endian_int),
                ('gasLimit', big_endian_int),
                ('chainId', big_endian_int),
            )
        return UnsignedChainId

    @staticmethod
    def SignedChainId():
        class SignedChainId(HashableRLP):
            fields = EditValidator.UnsignedChainId()._meta.fields[:-1] + ( # drop chainId
                ("v", big_endian_int),
                ("r", big_endian_int),
                ("s", big_endian_int),
            )
        return SignedChainId

    @staticmethod
    def Unsigned():
        class Unsigned(HashableRLP):
            fields = EditValidator.UnsignedChainId()._meta.fields[:-1]  # drop chainId
        return Unsigned

    @staticmethod
    def Signed():
        class Signed(HashableRLP):
            fields = EditValidator.Unsigned()._meta.fields[:-3] + (    # drop last 3 for raw.pop()
                ("v", big_endian_int),
                ("r", big_endian_int),
                ("s", big_endian_int),
            )
        return Signed
