# --*-- coding : utf-8 --*--
"""Author: Trinity Core Team

MIT License

Copyright (c) 2018 Trinity

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE."""
# system and 3rd party libs
# self modules import
import binascii
from wallet.utils import get_magic
from common.common import uri_parser
from common.log import LOG
from common.exceptions import GoTo
from wallet.channel import Channel, EnumTradeType, EnumTradeState, EnumTradeRole, Payment
from wallet.Interface.gate_way import send_message
from .response import EnumResponseStatus
from trinity import IS_SUPPORTED_ASSET_TYPE
from wallet.event import event_machine
from wallet.event.contract_event import ContractEventInterface
from common.number import TrinityNumber

class MessageHeader(object):
    """
        Message header is used for event transaction.
    """
    def __init__(self, sender:str, receiver:str, message_type:str, channel_name:str, asset_type:str, nonce:int,
                 nego_nonce=None):
        # here we could use the regex expression to judge the ip format accurately.
        assert sender.__contains__('@'), 'Invalid sender<{}>.'.format(sender)
        assert receiver.__contains__('@'), 'Invalid receiver<{}>.'.format(receiver)
        assert sender != receiver, 'Sender should be different from receiver.'

        self.message_header = {
            'MessageType':message_type,
            'Sender': sender,
            'Receiver': receiver,
            'TxNonce': str(nonce),
            'ChannelName': channel_name,
            'AssetType': asset_type.upper(),
            'NetMagic': get_magic()
        }

        if not nego_nonce:
            nego_nonce = nonce

        if 1 < nego_nonce and nonce != nego_nonce:
            self.message_header.update({'ResetTxNonce': nego_nonce})


class Message(object):
    """
    {
        'MessageType': 'MessageType',
        'Sender': sender,
        'Receiver': receiver,
        'TxNonce': '0',
        'ChannelName': channel_name,
        'AssetType': 'TNC',
        'NetMagic': magic,
    }

    """
    _SETTLE_NONCE = 0
    _FOUNDER_NONCE = 1
    _message_name = None
    _contract_event_api = None

    def __init__(self, message):
        self.message = message

        self.sender = message.get('Sender')
        self.sender_address, _, _ = uri_parser(self.sender)
        self.receiver = message.get('Receiver')
        self.receiver_address, _, _, = uri_parser(self.receiver)

        self.message_type = message.get('MessageType', '')
        self.message_body = message.get('MessageBody')
        self.resign_body = self.message_body.get('ResignBody')
        self.channel_name = message.get('ChannelName')
        self.asset_type = message.get('AssetType','INVALID_ASSET').upper()
        self.net_magic = message.get('NetMagic')
        self.status = message.get('Status')
        self.comments = self.message.get('Comments')

        self.nonce = int(message.get('TxNonce'))
        try:
            self.nego_nonce = int(message.get('ResetTxNonce'))
        except:
            self.nego_nonce = None

        # transaction is triggered by command cline
        trigger_by_cli = int(self.message_body.get('TriggeredByCLI', 0))
        self.trigger_by_cli = True if trigger_by_cli else False

    @property
    def network_magic(self):
        return get_magic()

    @staticmethod
    def send(message):
        send_message(message)

    @staticmethod
    def create_message_header(sender:str, receiver:str, message_type:str, channel_name:str, asset_type:str, nonce:int,
                              nego_nonce=None):
        return MessageHeader(sender, receiver, message_type, channel_name, asset_type, nonce, nego_nonce).message_header

    def handle(self):
        LOG.info('Received Message<{}> from<{}> by channel<{}>'.format(self.message_type,
                                                                       self.sender_address,
                                                                       self.channel_name))
        pass

    @classmethod
    def contract_event_api(cls):
        if not cls._contract_event_api:
            cls._contract_event_api = ContractEventInterface()

        return cls._contract_event_api

    @classmethod
    def sign_content(cls, wallet, sign_type_list, sign_value_list, *args, start=3, **kwargs):
        """

        :param wallet:
        :param sign_type_list:
        :param sign_value_list:
        :param args:
        :param start:
        :param kwargs:
        :return:
        """
        if not (wallet and sign_type_list and sign_value_list):
            raise GoTo(
                EnumResponseStatus.RESPONSE_ILLEGAL_INPUT_PARAMETER,
                'Lack of mandatory parameters: wallet<{}>, type_list<{}>, value_list<{}>' \
                    .format(wallet, sign_type_list, sign_value_list)
            )

        kwargs.update({
            'typeList': sign_type_list,
            'valueList': sign_value_list,
            'privtKey': wallet._key.private_key_string

        })

        return cls.contract_event_api().sign_content(start, *args, **kwargs)

    @classmethod
    def check_asset_type(cls, asset_type):
        """

        :param asset_type:
        :return:
        """
        if not IS_SUPPORTED_ASSET_TYPE(asset_type):
            raise GoTo(EnumResponseStatus.RESONSE_ASSET_TYPE_NOT_SUPPORTED,
                       'Unsupported Asset type: \'{}\''.format(asset_type))

        return True, None

    @classmethod
    def check_url_format(cls, wallet_url):
        """

        :param wallet_url:
        :return:
        """
        # TODO: it's better to use regex expression to check the url's validity
        if not wallet_url.__contains__('@'):
            raise GoTo(EnumResponseStatus.RESPONSE_INVALID_URL_FORMAT,
                       'Invalid format of url<{}>. Should be like 0xYYYY@ip:port'.format(wallet_url))

    @classmethod
    def check_both_urls(cls, sender, receiver):
        """

        :param sender:
        :param receiver:
        :return:
        """
        # check whether the sender and receiver are correct url or not
        # TODO: it's better to use regex expression to check the url's validity
        try:
            return sender.__contains__('@') and receiver.__contains__('@')
        except Exception as error:
            LOG.error('Illegal URL is found. Exception: {}'.format(error))
            raise GoTo(EnumResponseStatus.RESPONSE_INVALID_URL_FORMAT,
                       'Invalid format of sender<{}> or receiver<{}>. Should be like 0xYYYY@ip:port' \
                       .format(sender, receiver))

    @classmethod
    def check_network_magic(cls, magic):
        """

        :param magic:
        :return:
        """
        net_magic = get_magic()
        if net_magic != magic:
            raise GoTo(EnumResponseStatus.RESPONSE_INVALID_NETWORK_MAGIC,
                       'Invalid network ID<{}>. should be equal to {}'.format(magic, net_magic))

        return True, net_magic

    @classmethod
    def check_signature(cls, wallet, expected, type_list, value_list, signature):
        """"""
        if not (wallet and type_list and value_list and signature):
            raise GoTo(
                EnumResponseStatus.RESPONSE_ILLEGAL_INPUT_PARAMETER,
                'Illegal input parameters: type_list<{}>, value_list<{}>, signature<{}>' \
                    .format(type_list, value_list, signature)
            )

        sign_hash =  cls.contract_event_api().solidity_hash(type_list, value_list)
        peer_wallet_address = wallet.recoverHash(sign_hash, signature)

        if expected == peer_wallet_address:
            return True
        else:
            LOG.error('Error data hash<{}> with value<{}> by signature<{}>'.format(binascii.b2a_hex(sign_hash),
                                                                                   value_list, signature))
            raise GoTo(
                EnumResponseStatus.RESPONSE_SIGNATURE_VERIFIED_ERROR,
                'Signature verification error: expected<{}>, parsed-address<{}>'.format(expected, peer_wallet_address)
            )

    @classmethod
    def check_wallet(cls, wallet):
        if not wallet:
            raise GoTo(
                EnumResponseStatus.RESPONSE_COMMON_ERROR_VOID_WALLET,
                'Wallet is NoneType.'
            )
        return True

    @classmethod
    def check_nonce(cls, nonce, channel_name='', is_founder=False):
        """

        :param channel_name:
        :param nonce:
        :return:
        """
        nonce = int(nonce)
        nego_trade = Channel.query_trade(channel_name, nonce)
        pre_trade = Channel.query_trade(channel_name, nonce-1)

        # to check whether the nonce is legal one or not
        if not (TransactionBase._FOUNDER_NONCE < nonce
                and pre_trade.state in [EnumTradeState.confirmed.name, EnumTradeState.confirmed_onchain.name]
                and ((nego_trade.state in [EnumTradeState.init.state] and is_founder)
                or (nego_trade.state in [EnumTradeState.confirming.state] and not is_founder))):
            raise GoTo(
                EnumResponseStatus.RESPONSE_TRADE_WITH_INCOMPATIBLE_NONCE,
                '{}::Channel<{}> has incompatible nonce<{}>, state<previous:{}, negotiated: {}>' \
                    .format(cls.__name__, channel_name, nonce, pre_trade.state, nego_trade.state))
        else:
            return True, nego_trade

    @classmethod
    def calculate_balance_after_payment(cls, balance, peer_balance, payment, hlock_to_rsmc=False, is_htlc_type=False):
        """

        :param balance: payer's balance
        :param peer_balance: payee's balance
        :param payment:
        :param hlock_to_rsmc: exchange htlc lock part to rsmc transaction
        :return:
        """
        # if payment has occurred, calculate the balance after payment
        payment = int(payment)
        if 0 < payment <= int(balance):
            if hlock_to_rsmc:
                return True, int(balance), int(peer_balance) + payment
            elif is_htlc_type:
                return True, int(balance) - payment, int(peer_balance)
            else:
                return True, int(balance) - payment, int(peer_balance) + payment
        elif payment > int(balance):
            raise GoTo(EnumResponseStatus.RESPONSE_TRADE_NO_ENOUGH_BALANCE_FOR_PAYMENT,
                       'Sender has not enough balance<{}> for payment<{}>'.format(balance, payment))
        else:
            raise GoTo(EnumResponseStatus.RESPONSE_TRADE_NEGATIVE_PAYMENT,
                       'Payment<{}> should not be negative number.'.format(payment))

    @classmethod
    def check_balance(cls, channel_name, asset_type, address, balance, peer_address, peer_balance,
                      hlock_to_rsmc=False, is_htcl_type=False, **kwargs):
        """

        :param channel_name:
        :param asset_type:
        :param address:
        :param balance:
        :param peer_address:
        :param peer_balance:
        :param hlock_to_rsmc:
        :param is_htcl_type:
        :param kwargs:
        :return:
        """
        try:
            # get channel if trade has already existed
            channel_set = Channel.query_channel(channel_name)[0]
            expected_balance = int(channel_set.balance.get(address).get(asset_type))
            expected_peer_balance = int(channel_set.balance.get(peer_address).get(asset_type))

            # to calculate the balance after payment
            if kwargs and kwargs.__contains__('payment'):
                _, expected_balance, expected_peer_balance = \
                    cls.calculate_balance_after_payment(expected_balance, expected_peer_balance,
                                                        kwargs.get('payment'), hlock_to_rsmc, is_htcl_type)

            if int(balance) == expected_balance and int(peer_balance) == expected_peer_balance:
                return True, expected_balance, expected_peer_balance
            else:
                raise GoTo(
                    EnumResponseStatus.RESPONSE_TRADE_BALANCE_UNMATCHED_BETWEEN_PEERS,
                    'Channel<{}> balances are unmatched between channel peers<self: {}, expected: {}. peers: {}, expected: {}>' \
                    .format(channel_name, balance, expected_balance, peer_balance, expected_peer_balance))
        except Exception as error:
            raise GoTo(EnumResponseStatus.RESPONSE_TRADE_BALANCE_ERROR,
                       'Channel<{}> not found or balance error. Exception: {}'.format(channel_name, error))

    @classmethod
    def common_check(cls, sender, receiver, asset_type, net_magic):
        """

        :param sender:
        :param receiver:
        :param asset_type:
        :param net_magic:
        :return:
        """
        cls.check_asset_type(asset_type)
        cls.check_network_magic(net_magic)
        cls.check_both_urls(sender, receiver)

    def verify(self):
        """

        :return:
        """
        self.common_check(self.sender, self.receiver, self.asset_type, self.net_magic)
        return True, None

    @classmethod
    def check_response_status(cls, status):
        """

        :param status:
        :return:
        """
        if EnumResponseStatus.RESPONSE_OK.name != status:
            LOG.error('Message {} with error status<{}>'.format(cls._message_name, status))
            return False

        return True

    @classmethod
    def send_error_response(cls, sender:str, receiver:str, channel_name:str, asset_type:str,
                       nonce:int, status=EnumResponseStatus.RESPONSE_FAIL, **kwargs):
        message = cls.create_message_header(receiver, sender, cls._message_name, channel_name, asset_type, nonce)
        message.update({'MessageBody': kwargs})

        if status:
            message.update({'Status': status.name})

        cls.send(message)

class TransactionBase(Message):
    """
        Descriptions: for RSMC or HTLC transaction
    """
    _htlc_sign_type_list = ['bytes32', 'address', 'address', 'uint256', 'uint256', 'bytes32']
    _rsmc_sign_type_list = ['bytes32', 'uint256', 'address', 'uint256', 'address', 'uint256', 'bytes32', 'bytes32']

    def get_payer_and_payee_address(self):
        """"""
        if self.role_index in [-1, 1]:
            self.payer = self.sender
            self.payer_address = self.sender_address
            self.payee = self.receiver
            self.payee_address = self.receiver_address
        else:
            self.payer = self.receiver
            self.payer_address = self.receiver_address
            self.payee = self.sender
            self.payee_address = self.sender_address

    @classmethod
    def check_rcode(cls, hashcode, rcode):
        """

        :param hashcode:
        :param rcode:
        :return:
        """
        if Payment.verify_hr(hashcode, rcode):
            return True
        else:
            raise GoTo(EnumResponseStatus.RESPONSE_TRADE_HASHR_NOT_MATCHED_WITH_RCODE,
                       'HashR<{}> is not compatible with Rcode<{}>'.format(hashcode, rcode))

    @classmethod
    def check_channel_state(cls, channel_name):
        channel = Channel(channel_name)

        if not channel.is_opened:
            raise GoTo(EnumResponseStatus.RESPONSE_CHANNEL_NOT_OPENED,
                       'Channel is not OPENED. Current tate<{}>'.format(channel.state))

        return channel

    @classmethod
    def check_payment(cls, channel_name, address, asset_type, payment):
        """

        :param channel_name:
        :param address:
        :param asset_type:
        :param payment:
        :return:
        """
        channel_balance = Channel(channel_name).balance
        balance = channel_balance.get(address).get(asset_type.upper())

        if not 0 < int(payment) <= int(balance):
            raise GoTo(EnumResponseStatus.RESPONSE_TRADE_NO_ENOUGH_BALANCE_FOR_PAYMENT,
                       'Invalid payment<{}>, payer balance<{}>'.format(payment, balance))

        return True, int(balance)

    @classmethod
    def update_balance_for_channel(cls, channel_name, asset_type, payer_address, payee_address, payment,
                                   is_hlock_to_rsmc=False, is_htlc_type=False):
        """

        :param channel_name:
        :param payer_address:
        :param payee_address:
        :param asset_type:
        :param payment:
        :param is_hlock_to_rsmc:
        :return:
        """
        channel = Channel(channel_name)

        try:
            asset_type = asset_type.upper()
            channel_balance = channel.balance
            channel_hlock = channel.hlock

            # calculate the left balance
            payer_balance = channel_balance.get(payer_address).get(asset_type)
            payee_balance = channel_balance.get(payee_address).get(asset_type)

            if is_hlock_to_rsmc:
                payer_hlock = channel_hlock.get(payer_address).get(asset_type)
                payer_hlock = cls.big_number_calculate(payer_hlock, payment, False)
                if 0 > payer_hlock:
                    raise GoTo(EnumResponseStatus.RESPONSE_TRADE_LOCKED_ASSET_LESS_THAN_PAYMENT,
                               'Why here? Payment<{}> should less than locked asset'.format(payment))
                channel_hlock.update({payer_address: {asset_type: str(payer_hlock)}})
                payee_balance = cls.big_number_calculate(payee_balance, payment)
            elif is_htlc_type:
                payer_hlock = channel_hlock.get(payer_address).get(asset_type)
                payer_hlock = cls.big_number_calculate(payer_hlock, payment)
                channel_hlock.update({payer_address: {asset_type: str(payer_hlock)}})
                payer_balance = cls.big_number_calculate(payer_balance, payment, False)
            else:
                payer_balance = cls.big_number_calculate(payer_balance, payment, False)
                payee_balance = cls.big_number_calculate(payee_balance, payment)

            if int(payer_balance) >= 0 and int(payee_balance) >= 0:
                Channel.update_channel(channel_name,
                                       balance={payer_address: {asset_type: str(payer_balance)},
                                                payee_address: {asset_type: str(payee_balance)}},
                                       hlock=channel_hlock)
            else:
                raise GoTo(EnumResponseStatus.RESPONSE_TRADE_NO_ENOUGH_BALANCE_FOR_PAYMENT,
                           'Payer has not enough balance for this payment<{}>'.format(payment))

        except Exception as error:
            raise GoTo(EnumResponseStatus.RESPONSE_TRADE_BALANCE_UPDATE_FAILED,
                       'Update channel<{}> balance error. payment<{}>. Exception: {}'.format(channel_name,
                                                                                             payment, error))

    @classmethod
    def big_number_calculate(cls, balance, payment, add=True):
        # calculate
        if add:
            result = int(balance) + int(payment)
        else:
            result = int(balance) - int(payment)

        return result

    @classmethod
    def hashr_is_valid_format(cls, hashcode):
        return isinstance(hashcode, str) and hashcode.startswith('TN')

    @classmethod
    def get_peer(cls, channel_set, source):
        if not channel_set:
            return None
        peer = channel_set.src_addr

        return peer if peer != source else channel_set.dest_addr

    @classmethod
    def rollback_resource(cls, channel_name, nonce, payment=None, status=None):
        """

        :param channel_name:
        :param nonce:
        :param payment:
        :return:
        """
        # For the healthy of the transaction, maybe we need give up the resources roll-back operation

        # if status is not None and status != EnumResponseStatus.RESPONSE_OK.name:
        #     trade = Channel.query_trade(channel_name, nonce)
        #     if trade:
        #         Channel.delete_trade(channel_name, int(nonce))
        pass

    @classmethod
    def is_hlock_to_rsmc(cls, hashcode):
        if isinstance(hashcode, str):
            hashcode = hashcode.strip(' 0x')
        return hashcode not in [None, '']

    @classmethod
    def get_rcode(cls, channel_name, hashcode):
        """

        :param hashcode:
        :return:
        """
        if cls.is_hlock_to_rsmc(hashcode):
            htlc_trade = Channel.batch_query_trade(channel_name,
                                                   filters={'type': EnumTradeType.TRADE_TYPE_HTLC.name,
                                                            'hashcode': hashcode})
            if not htlc_trade:
                raise GoTo(EnumResponseStatus.RESPONSE_TRADE_HASHR_NOT_FOUND,
                           'Htlc trade with HashR<{}> NOT found'.format(hashcode))
            htlc_trade = htlc_trade[0]
            return hashcode, htlc_trade.rcode
        else:
            return cls.get_default_rcode()

    @classmethod
    def get_default_rcode(cls):
        return Channel._trade_hash_rcode_default, Channel._trade_hash_rcode_default

    @classmethod
    def get_htlc_trade_by_hashr(cls, channel_name, hashcode):
        """

        :param channel_name:
        :param hashcode:
        :return:
        """
        try:
            return Channel.batch_query_trade(channel_name, filters={'type': EnumTradeType.TRADE_TYPE_HTLC.name,
                                                                    'hashcode': hashcode})[0]
        except Exception as error:
            raise GoTo(EnumResponseStatus.RESPONSE_TRADE_HASHR_NOT_FOUND,
                       'Htlc trade not found by the HashR<{}> in channel<{}>'.format(hashcode, channel_name))

    @classmethod
    def create_resign_message_body(cls, wallet_url, channel_name, nonce, is_resign_response=False):
        """

        :param wallet_url: current opened wallet url
        :param channel_name:
        :param nonce:
        :param is_resign_response: if True, it means has received the peer resign request
        :return:
        """
        if not (channel_name and isinstance(nonce, int)):
            raise GoTo(
                EnumResponseStatus.RESPONSE_ILLEGAL_INPUT_PARAMETER,
                'Illegal parameters: channel_name<{}> or nonce<{}, type:{}>'.format(channel_name, nonce, type(nonce))
            )

        # record the nonce negotiated by partner of the transaction with nonce
        # required is True
        if is_resign_response:
            nego_nonce = nonce
            pre_nonce = nonce
            pre_trade = Channel.query_trade(channel_name, nego_nonce)
        else:
            pre_trade, pre_nonce = Channel.latest_valid_trade(channel_name)
            nego_nonce = pre_nonce + 1 if isinstance(pre_nonce, int) else None

        # does founder practise fraud ???
        if not (pre_trade and nego_nonce) or cls._FOUNDER_NONCE >= nego_nonce:
            raise GoTo(
                EnumResponseStatus.RESPONSE_TRADE_WITH_INCORRECT_NONCE,
                'Could not finish this transaction because '
            )

        # initialize some local variables
        trade_role = pre_trade.role
        trade_type = pre_trade.type
        trade_state = pre_trade.state

        # local variables
        resign_body = {'Nonce': str(pre_nonce)}
        need_update_balance = False

        # is partner role of this transaction
        if EnumTradeRole.TRADE_ROLE_PARTNER.name == trade_role and EnumTradeState.confirming.name == trade_state:
            # update the resign_body by the different trade type
            if EnumTradeType.TRADE_TYPE_RSMC.name == trade_type:
                if pre_trade.peer_commitment:
                    # update the trade to confirmed state directly
                    Channel.update_trade(channel_name, pre_nonce, state=EnumTradeState.confirmed.name)

                    # need update channel balance
                    # need_update_balance = True
                else:
                    # need resign this RSMC transaction by peer
                    resign_body.update({'Commitment': pre_trade.commitment})
            elif EnumTradeType.TRADE_TYPE_HTLC.name == trade_type:
                if pre_trade.peer_commitment and pre_trade.peer_delay_commitment:
                    # update the trade to confirmed state directly
                    Channel.update_trade(channel_name, pre_nonce, state=EnumTradeState.confirming.name)
                    # need update channel balance
                    # need_update_balance = True
                else:
                    # need resign this HTLC transaction by peer
                    resign_body.update({'Commitment': pre_trade.commitment})
                    resign_body.update({'DelayCommitment': pre_trade.delay_commitment})
            else:
                LOG.error('Unsupported trade type<{}> to resign in partner<{}> side'
                          .format(trade_type, wallet_url))
                return None, pre_trade

            # need update the channel balance or not???
            # if need_update_balance:
            #     channel = Channel(channel_name)
            #     self_address, _, _ = uri_parser(wallet_url)
            #     peer_address, _, _ = uri_parser(channel.peer_uri(wallet_url))
            #
            #     # ToDo: if need, here need add asset type check in future
            #     Channel.update_channel(channel_name, balance={
            #         self_address: {pre_trade.asset_type: pre_trade.balance},
            #         peer_address: {pre_trade.asset_type: pre_trade.peer_balance}
            #     })
        elif is_resign_response is True and EnumTradeRole.TRADE_ROLE_FOUNDER.name == trade_role and \
                (EnumTradeState.confirmed.name == trade_state or (EnumTradeState.confirming.name == trade_state and
                                                                  EnumTradeType.TRADE_TYPE_HTLC.name == trade_type)):

            if EnumTradeType.TRADE_TYPE_RSMC.name == trade_type:
                if pre_trade.peer_commitment:
                    resign_body.update({'Commitment': pre_trade.commitment})
                else:
                    # stop transaction ????
                    pass
            elif EnumTradeType.TRADE_TYPE_HTLC.name == trade_type:
                if pre_trade.peer_commitment and pre_trade.peer_delay_commitment:
                    resign_body.update({'Commitment': pre_trade.commitment})
                    resign_body.update({'DelayCommitment': pre_trade.delay_commitment})
            else:
                LOG.error('Unsupported trade type<{}> to resign in founder<{}> side'
                          .format(trade_type, wallet_url))
                return None, pre_trade
        else:
            return None, pre_trade

        # Previous transaction need to be resigned by peer
        if 'Commitment' in resign_body:
            return resign_body, pre_trade

        return None, pre_trade

    @classmethod
    def handle_resign_body(cls, wallet, channel_name, resign_body):
        """"""
        if not resign_body:
            return None

        if not (wallet and channel_name):
            raise GoTo(
                EnumResponseStatus.RESPONSE_ILLEGAL_INPUT_PARAMETER,
                'Void wallet<{}> or Illegal channel name<{}>'.format(wallet, channel_name)
            )

        resign_nonce = int(resign_body.get('Nonce'))
        update_channel_db = {}
        update_trade_db = {}

        # get transaction by the resign_nonce
        resign_trade = Channel.query_trade(channel_name, resign_nonce)
        asset_type = resign_trade.asset_type

        channel = Channel(channel_name)
        self_address, _, _ = uri_parser(wallet.url)
        peer_address, _, _ = uri_parser(channel.peer_uri(wallet.url))

        # Is this wallet sender side of this trade ?????
        if EnumTradeRole.TRADE_ROLE_PARTNER.name == resign_trade.role:
            payer_address = peer_address
            payee_address = self_address
            payer_balance = resign_trade.peer_balance
            payee_balance = resign_trade.balance
        elif EnumTradeRole.TRADE_ROLE_FOUNDER.name == resign_trade.role:
            payer_address = self_address
            payee_address = peer_address
            payer_balance = resign_trade.balance
            payee_balance = resign_trade.peer_balance
        else:
            raise GoTo(
                EnumResponseStatus.RESPONSE_DATABASE_ERROR_TRADE_ROLE,
                'Error trade role<{}> for nonce<{}>'.format(resign_trade.role, resign_nonce)
            )

        if EnumTradeType.TRADE_TYPE_HTLC.name == resign_trade.type:
            rsmc_sign_hashcode, rsmc_sign_rcode = cls.get_default_rcode()
        elif EnumTradeType.TRADE_TYPE_RSMC.name == resign_trade.type:
            rsmc_sign_hashcode, rsmc_sign_rcode = resign_trade.hashcode, resign_trade.rcode
        else:
            raise GoTo(
                EnumResponseStatus.RESPONSE_TRADE_RESIGN_NOT_SUPPORTED_TYPE,
                'Only support HTLC and RSMC resign. Current transaction type: {}'.format(resign_trade.type)
            )

        rsmc_list = [channel_name, resign_nonce, payer_address, int(payer_balance),
                     payee_address, int(payee_balance), rsmc_sign_hashcode, rsmc_sign_rcode]

        # self commitment is None
        if not resign_trade.commitment:
            commitment = cls.sign_content( wallet, cls._rsmc_sign_type_list, rsmc_list)
            update_trade_db.update({'commitment': commitment})

        # self lock commitment is none
        htlc_list = None
        if EnumTradeType.TRADE_TYPE_HTLC.name == resign_trade.type:
            htlc_list = [channel_name, payer_address, payee_address, int(resign_trade.delay_block),
                         int(resign_trade.payment), resign_trade.hashcode]
            delay_commitment = cls.sign_content(wallet, cls._htlc_sign_type_list, htlc_list)
            update_trade_db.update({'delay_commitment': delay_commitment})

        # check whether the trade need update or not
        if resign_trade.state not in [EnumTradeState.confirmed.name, EnumTradeState.confirmed_onchain.name]:
            peer_commitment = resign_body.get('Commitment')
            peer_delay_commitment = resign_body.get('DelayCommitment')

            # check peer signature
            cls.check_signature(wallet, peer_address, cls._rsmc_sign_type_list, rsmc_list, peer_commitment)

            # update transaction and channel balance
            update_trade_db.update({'peer_commitment': peer_commitment})
            update_channel_db = {
                'balance': {
                    payer_address: {asset_type: payer_balance},
                    payee_address: {asset_type: payee_balance}
                }
            }

            # check the htlc part signature
            if EnumTradeType.TRADE_TYPE_HTLC.name == resign_trade.type:
                cls.check_signature(wallet, peer_address, cls._htlc_sign_type_list,
                                    htlc_list, peer_delay_commitment)
                update_trade_db.update({'peer_delay_commitment': peer_delay_commitment})
                update_trade_db.update({'state': EnumTradeState.confirming.name})

                # need update the hlock part
                if payer_address == self_address:
                    channel = Channel(channel_name)
                    channel_hlock = channel.hlock
                    if channel_hlock and channel_hlock.get(payer_address):
                        payer_hlock = int(resign_trade.payment) + int(channel_hlock.get(payer_address).get(asset_type))
                        channel_hlock.update({payer_address: {asset_type: str(payer_hlock)}})
                        update_channel_db.update({'hlock': channel_hlock})
            else: # RSMC type transaction
                update_trade_db.update({'state': EnumTradeState.confirmed.name})

            # update transaction
            Channel.update_trade(channel_name, resign_nonce, **update_trade_db)

            # update Channel balance to new one
            Channel.update_channel(channel_name, **update_channel_db)

        # return resign message body
        return cls.create_resign_message_body(wallet.url, channel_name, resign_nonce, True)

    def validate_negotiated_nonce(self):
        """

        :return:
        """
        # to validate the negotiated nonce
        valid_trade, valid_nonce = Channel.latest_valid_trade(self.channel_name)

        nonce = self.nego_nonce or self.nonce
        if valid_nonce and valid_nonce+1 == nonce:
            return valid_trade
        else:
            raise GoTo(
                EnumResponseStatus.RESPONSE_TRADE_COULD_NOT_BE_OVERWRITTEN,
                'Could not use negotiated nonce <{}>, current valid nonce<{}>'.format(self.nego_nonce, valid_nonce)
            )
    
    def record_transaction(self, nonce, **update_args):
        """
        
        :param nonce:
        :param update_args:
        :return:
        """
        # check the trade with nonce existed or not
        try:
            new_trade = Channel.query_trade(self.channel_name, nonce)
        except Exception as error:
            new_trade = None

        # add new transaction
        if new_trade:
            Channel.update_trade(self.channel_name, nonce=nonce, **update_args)
        else:
            Channel.add_trade(self.channel_name, nonce=nonce, **update_args)

        return

    def validate_transaction(self):
        """

        :return:
        """
        # check the trade with nonce existed or not
        try:
            pre_trade = Channel.query_trade(self.channel_name, self.nonce-1)
        except Exception as error:
            # should never go here
            raise(
                EnumResponseStatus.RESPONSE_TRADE_NOT_FOUND,
                'Transaction with nonce<{}> is not found'.format(self.nonce-1)
            )
        else:
            if pre_trade.state in [EnumTradeState.confirmed.name or EnumTradeState.confirmed_onchain.name]:
                return True
            elif pre_trade.state in [EnumTradeState.confirming.name] \
                    and EnumTradeType.TRADE_TYPE_HTLC.name == pre_trade.type \
                    and pre_trade.peer_commitment and pre_trade.peer_delay_commitment:
                return True

            raise GoTo(
                EnumResponseStatus.RESPONSE_TRADE_RESIGN_REQUEST_NOT_IMPLEMENTED,
                'Wait for next resign. current confirmed nonce<{}>, request nonce<{}>'.format(
                    Channel.latest_confirmed_nonce(), self.nonce)
            )
