from typing import List, Protocol, Sequence

from dharitri_sdk_core.address import Address
from dharitri_sdk_core.constants import DELEGATION_MANAGER_SC_ADDRESS
from dharitri_sdk_core.errors import ErrListsLengthMismatch
from dharitri_sdk_core.interfaces import IAddress, IValidatorPublicKey
from dharitri_sdk_core.serializer import arg_to_string
from dharitri_sdk_core.transaction_intents_factories.transaction_intent_builder import \
    TransactionIntentBuilder
from dharitri_sdk_core.transaction_intent import TransactionIntent


class IConfig(Protocol):
    chain_id: str
    min_gas_limit: int
    gas_limit_per_byte: int
    gas_limit_stake: int
    gas_limit_unstake: int
    gas_limit_unbond: int
    gas_limit_create_delegation_contract: int
    gas_limit_delegation_operations: int
    additional_gas_limit_per_validator_node: int
    additional_gas_for_delegation_operations: int


class DelegationTransactionIntentsFactory:
    def __init__(self, config: IConfig) -> None:
        self.config = config

    def create_transaction_intent_for_new_delegation_contract(self,
                                                              sender: IAddress,
                                                              total_delegation_cap: int,
                                                              service_fee: int,
                                                              value: int) -> TransactionIntent:
        parts = [
            "createNewDelegationContract",
            arg_to_string(total_delegation_cap),
            arg_to_string(service_fee)
        ]

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=Address.from_bech32(DELEGATION_MANAGER_SC_ADDRESS),
            data_parts=parts,
            execution_gas_limit=self.config.gas_limit_create_delegation_contract + self.config.additional_gas_for_delegation_operations,
            value=value
        ).build()

        return intent

    def create_transaction_intent_for_adding_nodes(self,
                                                   sender: IAddress,
                                                   delegation_contract: IAddress,
                                                   public_keys: Sequence[IValidatorPublicKey],
                                                   signed_messages: List[bytes]) -> TransactionIntent:
        if len(public_keys) != len(signed_messages):
            raise ErrListsLengthMismatch("The number of public keys should match the number of signed messages")

        parts = ["addNodes"]
        for i in range(len(public_keys)):
            parts.append(public_keys[i].hex())
            parts.append(signed_messages[i].hex())

        num_nodes = len(public_keys)

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self._compute_execution_gas_limit_for_nodes_management(num_nodes)
        ).build()

        return intent

    def _compute_execution_gas_limit_for_nodes_management(self, num_nodes: int) -> int:
        return self.config.gas_limit_delegation_operations + num_nodes * self.config.additional_gas_limit_per_validator_node

    def create_transaction_intent_for_removing_nodes(self,
                                                     sender: IAddress,
                                                     delegation_contract: IAddress,
                                                     public_keys: Sequence[IValidatorPublicKey]) -> TransactionIntent:
        num_nodes = len(public_keys)

        parts: List[str] = ["removeNodes"]
        for public_key in public_keys:
            parts.append(public_key.hex())

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self._compute_execution_gas_limit_for_nodes_management(num_nodes)
        ).build()

        return intent

    def create_transaction_intent_for_staking_nodes(self,
                                                    sender: IAddress,
                                                    delegation_contract: IAddress,
                                                    public_keys: Sequence[IValidatorPublicKey]) -> TransactionIntent:
        num_nodes = len(public_keys)

        parts = ["stakeNodes"]
        for public_key in public_keys:
            parts.append(public_key.hex())

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self.config.gas_limit_delegation_operations + self.config.gas_limit_stake + num_nodes * self.config.additional_gas_limit_per_validator_node
        ).build()

        return intent

    def create_transaction_intent_for_unbonding_nodes(self,
                                                      sender: IAddress,
                                                      delegation_contract: IAddress,
                                                      public_keys: Sequence[IValidatorPublicKey]) -> TransactionIntent:
        num_nodes = len(public_keys)

        parts = ["unBondNodes"]
        for public_key in public_keys:
            parts.append(public_key.hex())

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self.config.gas_limit_delegation_operations + self.config.gas_limit_unbond + num_nodes * self.config.additional_gas_limit_per_validator_node
        ).build()

        return intent

    def create_transaction_intent_for_unstaking_nodes(self,
                                                      sender: IAddress,
                                                      delegation_contract: IAddress,
                                                      public_keys: Sequence[IValidatorPublicKey]) -> TransactionIntent:
        num_nodes = len(public_keys)

        parts = ["unStakeNodes"]
        for public_key in public_keys:
            parts.append(public_key.hex())

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self.config.gas_limit_delegation_operations + self.config.gas_limit_unstake + num_nodes * self.config.additional_gas_limit_per_validator_node
        ).build()

        return intent

    def create_transaction_intent_for_unjailing_nodes(self,
                                                      sender: IAddress,
                                                      delegation_contract: IAddress,
                                                      public_keys: Sequence[IValidatorPublicKey]) -> TransactionIntent:
        num_nodes = len(public_keys)

        parts = ["unJailNodes"]
        for public_key in public_keys:
            parts.append(public_key.hex())

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self._compute_execution_gas_limit_for_nodes_management(num_nodes)
        ).build()

        return intent

    def create_transaction_intent_for_changing_service_fee(self,
                                                           sender: IAddress,
                                                           delegation_contract: IAddress,
                                                           service_fee: int) -> TransactionIntent:
        parts = [
            "changeServiceFee",
            arg_to_string(service_fee)
        ]

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self.config.gas_limit_delegation_operations + self.config.additional_gas_for_delegation_operations
        ).build()

        return intent

    def create_transaction_intent_for_modifying_delegation_cap(self,
                                                               sender: IAddress,
                                                               delegation_contract: IAddress,
                                                               delegation_cap: int) -> TransactionIntent:
        parts = [
            "modifyTotalDelegationCap",
            arg_to_string(delegation_cap)
        ]

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self.config.gas_limit_delegation_operations + self.config.additional_gas_for_delegation_operations
        ).build()

        return intent

    def create_transaction_intent_for_setting_automatic_activation(self,
                                                                   sender: IAddress,
                                                                   delegation_contract: IAddress) -> TransactionIntent:
        parts = [
            "setAutomaticActivation",
            arg_to_string('true')
        ]

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self.config.gas_limit_delegation_operations + self.config.additional_gas_for_delegation_operations
        ).build()

        return intent

    def create_transaction_intent_for_unsetting_automatic_activation(self,
                                                                     sender: IAddress,
                                                                     delegation_contract: IAddress) -> TransactionIntent:
        parts = [
            "setAutomaticActivation",
            arg_to_string('false')
        ]

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self.config.gas_limit_delegation_operations + self.config.additional_gas_for_delegation_operations
        ).build()

        return intent

    def create_transaction_intent_for_setting_cap_check_on_redelegate_rewards(self,
                                                                              sender: IAddress,
                                                                              delegation_contract: IAddress) -> TransactionIntent:
        parts = [
            "setCheckCapOnReDelegateRewards",
            arg_to_string('true')
        ]

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self.config.gas_limit_delegation_operations + self.config.additional_gas_for_delegation_operations
        ).build()

        return intent

    def create_transaction_intent_for_unsetting_cap_check_on_redelegate_rewards(self,
                                                                                sender: IAddress,
                                                                                delegation_contract: IAddress) -> TransactionIntent:
        parts = [
            "setCheckCapOnReDelegateRewards",
            arg_to_string('false')
        ]

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self.config.gas_limit_delegation_operations + self.config.additional_gas_for_delegation_operations
        ).build()

        return intent

    def create_transaction_intent_for_setting_metadata(self,
                                                       sender: IAddress,
                                                       delegation_contract: IAddress,
                                                       name: str,
                                                       website: str,
                                                       identifier: str) -> TransactionIntent:
        parts = [
            "setMetaData",
            arg_to_string(name),
            arg_to_string(website),
            arg_to_string(identifier)
        ]

        intent = TransactionIntentBuilder(
            config=self.config,
            sender=sender,
            receiver=delegation_contract,
            data_parts=parts,
            execution_gas_limit=self.config.gas_limit_delegation_operations + self.config.additional_gas_for_delegation_operations
        ).build()

        return intent
